city-select.vue 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. <template>
  2. <view>
  3. <text
  4. class="uni-input"
  5. @tap="open"
  6. >{{ value }}</text>
  7. <uv-popup
  8. ref="popup"
  9. mode="bottom"
  10. >
  11. <view class="cityselect">
  12. <view class="cityselect-header">
  13. <view class="cityselect-title">
  14. <text>请选择地址</text>
  15. </view>
  16. <view class="cityselect-nav">
  17. <view
  18. class="item"
  19. v-if="provinceActive"
  20. @tap="changeNav(0)"
  21. >
  22. <text>{{ provinceActive.name }}</text>
  23. </view>
  24. <view
  25. class="item"
  26. v-if="cityActive"
  27. @tap="changeNav(1)"
  28. >
  29. <text>{{ cityActive.name }}</text>
  30. </view>
  31. <view
  32. class="item"
  33. v-if="districtActive"
  34. @tap="changeNav(2)"
  35. >
  36. <text>{{ districtActive.name }}</text>
  37. </view>
  38. <view
  39. class="item active"
  40. v-else
  41. >
  42. <text>请选择</text>
  43. </view>
  44. </view>
  45. </view>
  46. <view class="cityselect-content">
  47. <swiper
  48. class="swiper"
  49. disable-touch="true"
  50. touchable="false"
  51. :current="current"
  52. >
  53. <swiper-item>
  54. <scroll-view
  55. scroll-y
  56. class="cityScroll"
  57. >
  58. <view>
  59. <view
  60. class="cityselect-item"
  61. v-for="(item, index) in province"
  62. :key="index"
  63. @tap="selectProvince(item, index)"
  64. >
  65. <view
  66. class="cityselect-item-box"
  67. :class="{ active: isProvinceActive(item) }"
  68. >
  69. <text>{{ item.name }}</text>
  70. </view>
  71. </view>
  72. </view>
  73. </scroll-view>
  74. </swiper-item>
  75. <swiper-item>
  76. <scroll-view
  77. scroll-y
  78. class="cityScroll"
  79. >
  80. <view>
  81. <view
  82. class="cityselect-item"
  83. v-for="(item, index) in city"
  84. :key="index"
  85. @tap="selectCity(item, index)"
  86. >
  87. <view
  88. class="cityselect-item-box"
  89. :class="{ active: isCityActive(item) }"
  90. >
  91. <text>{{ item.name }}</text>
  92. </view>
  93. </view>
  94. </view>
  95. </scroll-view>
  96. </swiper-item>
  97. <swiper-item>
  98. <scroll-view
  99. scroll-y
  100. class="cityScroll"
  101. >
  102. <view>
  103. <view
  104. class="cityselect-item"
  105. v-for="(item, index) in district"
  106. :key="index"
  107. @tap="selectDistrict(item, index)"
  108. >
  109. <view
  110. class="cityselect-item-box"
  111. :class="{ active: isDistrictActive(item) }"
  112. >
  113. <text>{{ item.name }}</text>
  114. </view>
  115. </view>
  116. </view>
  117. </scroll-view>
  118. </swiper-item>
  119. </swiper>
  120. </view>
  121. </view>
  122. </uv-popup>
  123. </view>
  124. </template>
  125. <script setup>
  126. import { ref, watch,onMounted } from "vue"
  127. const props = defineProps(['items', 'defaultValue'])
  128. const emit = defineEmits(['callback'])
  129. const items = ref(props.items || [])
  130. // const defaultValue = ref(props.defaultValue)
  131. // console.log("--> % defaultValue:\n", defaultValue)
  132. const value = ref(props.value || '请选择')
  133. const province = ref(props.items)
  134. const provinceActive = ref(null)
  135. const city = ref([])
  136. const cityActive = ref(null)
  137. const district = ref([])
  138. const districtActive = ref(null)
  139. const current = ref(0)
  140. const popup = ref(null)
  141. watch(() => props.items, (next) => {
  142. province.value = next
  143. })
  144. watch(() => props.defaultValue, (next) => {
  145. console.log("--> % defaultValue % next:\n", next)
  146. value.value = `${next.province.name} ${next.city.name} ${next.district.name}`
  147. setDefaultValue(items.value, next)
  148. })
  149. onMounted(() => {
  150. // setDefaultValue(items, props.defaultValue)
  151. })
  152. const isProvinceActive = (item) => {
  153. return provinceActive.value && item.value == provinceActive.value.value
  154. }
  155. const isCityActive = (item) => {
  156. return cityActive.value && item.value == cityActive.value.value
  157. }
  158. const isDistrictActive = (item) => {
  159. return districtActive.value && item.value == districtActive.value.value
  160. }
  161. const setDefaultValue = (items, value) => {
  162. if (!items || !value) {
  163. return
  164. }
  165. province.value = items
  166. items.map(prov => {
  167. console.log("--> % setDefaultValue % prov:\n", prov)
  168. if (prov.name == value.province.name) {
  169. city.value = prov.id
  170. provinceActive.value = {
  171. value: prov.id,
  172. name: value.province.name,
  173. }
  174. prov.children.map(city => {
  175. if (city.name == value.city.name) {
  176. district.value = city.children
  177. cityActive.value = {
  178. value: city.id,
  179. name: value.city.name,
  180. }
  181. city.children.map(district => {
  182. if (district.name == value.district.name) {
  183. districtActive.value = {
  184. value: city.id,
  185. name: value.district.name,
  186. }
  187. }
  188. })
  189. }
  190. })
  191. }
  192. })
  193. console.log(provinceActive.value, cityActive.value, districtActive.value)
  194. }
  195. const open = () => {
  196. province.value = props.items
  197. provinceActive.value = null
  198. cityActive.value = null
  199. districtActive.value = null
  200. city.value = []
  201. district.value = []
  202. current.value = 0
  203. popup.value.open()
  204. setDefaultValue(items.value, props.defaultValue.value)
  205. }
  206. const changeNav = (index) => {
  207. if (index == 0) {
  208. provinceActive.value = null
  209. }
  210. if (index == 1) {
  211. cityActive.value = null
  212. districtActive.value = null
  213. }
  214. if (index == 2) {
  215. districtActive.value = null
  216. }
  217. current.value = index
  218. }
  219. const selectProvince = (selectItem, index) => {
  220. provinceActive.value = selectItem
  221. city.value = selectItem.children
  222. current.value = 1
  223. }
  224. const selectCity = (selectItem, index) => {
  225. cityActive.value = selectItem
  226. district.value = selectItem.children
  227. current.value = 2
  228. }
  229. const selectDistrict = (selectItem, index) => {
  230. console.log("--> % selectDistrict % selectItem:\n", selectItem)
  231. districtActive.value = selectItem
  232. value.value = `${provinceActive.value?.name} ${cityActive.value?.name} ${districtActive.value?.name}`
  233. console.log({
  234. province: {
  235. id: provinceActive.value?.id,
  236. name: provinceActive.value?.name,
  237. },
  238. city: {
  239. id: cityActive.value?.id,
  240. name: cityActive.value?.name,
  241. },
  242. district: {
  243. id: districtActive.value?.id,
  244. name: districtActive.value?.name,
  245. },
  246. })
  247. emit('callback', {
  248. province: {
  249. id: provinceActive.value?.id,
  250. name: provinceActive.value?.name,
  251. },
  252. city: {
  253. id: cityActive.value?.id,
  254. name: cityActive.value?.name,
  255. },
  256. district: {
  257. id: districtActive.value?.id,
  258. name: districtActive.value?.name,
  259. },
  260. })
  261. popup.value.close()
  262. }
  263. </script>
  264. <style lang="less">
  265. .cityselect {
  266. width: 100%;
  267. height: 75%;
  268. background-color: #fff;
  269. z-index: 1502;
  270. position: relative;
  271. padding-bottom: 0;
  272. padding-bottom: constant(safe-area-inset-bottom);
  273. padding-bottom: env(safe-area-inset-bottom);
  274. .cityScroll {
  275. height: 100%;
  276. }
  277. .swiper {
  278. height: 800rpx;
  279. }
  280. }
  281. .cityselect-header {
  282. width: 100%;
  283. z-index: 1;
  284. }
  285. .cityselect-title {
  286. width: 100%;
  287. font-size: 30rpx;
  288. text-align: center;
  289. height: 95rpx;
  290. line-height: 95rpx;
  291. position: relative;
  292. &:cityselect-title:after {
  293. height: 1px;
  294. position: absolute;
  295. z-index: 0;
  296. bottom: 0;
  297. left: 0;
  298. content: '';
  299. width: 100%;
  300. background-image: linear-gradient(0deg, #ececec 50%, transparent 0);
  301. }
  302. }
  303. .cityselect-nav {
  304. width: 100%;
  305. padding-left: 20rpx;
  306. overflow: hidden;
  307. display: flex;
  308. align-items: center;
  309. justify-content: flex-start;
  310. .item {
  311. font-size: 26rpx;
  312. color: #222;
  313. display: block;
  314. height: 80rpx;
  315. line-height: 92rpx;
  316. padding: 0 16rpx;
  317. position: relative;
  318. margin-right: 30rpx;
  319. white-space: nowrap;
  320. overflow: hidden;
  321. text-overflow: ellipsis;
  322. max-width: 40%;
  323. &.active {
  324. color: #f23030 !important;
  325. border-bottom: 1rpx solid #f23030;
  326. }
  327. }
  328. }
  329. .cityselect-content {
  330. height: 100%;
  331. width: 100%;
  332. }
  333. .cityselect-item {
  334. .cityselect-item-box {
  335. display: block;
  336. padding: 0 40rpx;
  337. position: relative;
  338. overflow: hidden;
  339. display: -webkit-box;
  340. -webkit-line-clamp: 2;
  341. -webkit-box-orient: vertical;
  342. word-break: break-all;
  343. text-overflow: ellipsis;
  344. line-height: 64rpx;
  345. max-height: 65rpx;
  346. font-size: 26rpx;
  347. color: #333;
  348. &.active {
  349. color: #f23030 !important;
  350. }
  351. &:after {
  352. content: '';
  353. height: 1rpx;
  354. position: absolute;
  355. z-index: 0;
  356. bottom: 0;
  357. left: 0;
  358. width: 100%;
  359. background-image: linear-gradient(0deg, #ececec 50%, transparent 0);
  360. }
  361. }
  362. }
  363. </style>