addAddress.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. <template>
  2. <view class="switch-roles">
  3. <nav-bar title="添加地址" color="#000"></nav-bar>
  4. <view class="roles-content">
  5. <view class="content">
  6. <!-- 地图选点入口(辅助填充) -->
  7. <view class="map-selector" @click="openMapSelector">
  8. <u-icon name="map" size="36rpx" color="#007AFF" marginRight="16rpx"></u-icon>
  9. <text class="map-text">通过地图选择地址(可选)</text>
  10. </view>
  11. <!-- 省/市/区三级联动选择器 -->
  12. <view class="form-item">
  13. <view class="item-label">所在省份 <text class="required">*</text></view>
  14. <picker
  15. :range="provinces"
  16. :range-key="'name'"
  17. v-model="provinceIndex"
  18. @change="onProvinceChange"
  19. >
  20. <view class="picker-view">
  21. {{ provinceIndex !== null ? provinces[provinceIndex].name : '请选择省份' }}
  22. </view>
  23. </picker>
  24. </view>
  25. <view class="form-item">
  26. <view class="item-label">所在城市 <text class="required">*</text></view>
  27. <picker
  28. :range="cities"
  29. :range-key="'name'"
  30. v-model="cityIndex"
  31. @change="onCityChange"
  32. :disabled="provinceIndex === null"
  33. >
  34. <view class="picker-view">
  35. {{ cityIndex !== null && cities.length > 0 ? cities[cityIndex].name : '请先选择省份' }}
  36. </view>
  37. </picker>
  38. </view>
  39. <view class="form-item">
  40. <view class="item-label">所在区域 <text class="required">*</text></view>
  41. <picker
  42. :range="districts"
  43. :range-key="'name'"
  44. v-model="districtIndex"
  45. @change="onDistrictChange"
  46. :disabled="cityIndex === null || cities.length === 0"
  47. >
  48. <view class="picker-view">
  49. {{ districtIndex !== null && districts.length > 0 ? districts[districtIndex].name : '请先选择城市' }}
  50. </view>
  51. </picker>
  52. </view>
  53. <!-- 详细地址信息 -->
  54. <view class="form-item">
  55. <view class="item-label">街道/楼宇 <text class="required">*</text></view>
  56. <u-input
  57. placeholder="请输入街道或楼宇名称,例:科技园路1号"
  58. v-model="address"
  59. clearable
  60. class="custom-input"
  61. >
  62. <template #prefix>
  63. <u-icon name="map" size="36rpx" color="#999" marginRight="20rpx"></u-icon>
  64. </template>
  65. </u-input>
  66. </view>
  67. <view class="form-item">
  68. <view class="item-label">楼层/单元室</view>
  69. <u-input
  70. placeholder="例:3层302室(选填)"
  71. v-model="addressDetail"
  72. clearable
  73. class="custom-input"
  74. >
  75. <template #prefix>
  76. <u-icon name="home" size="36rpx" color="#999" marginRight="20rpx"></u-icon>
  77. </template>
  78. </u-input>
  79. </view>
  80. <!-- 地图选点后显示经纬度(可选) -->
  81. <view class="location-info" v-if="latitude && longitude">
  82. <text>经纬度:{{ latitude }}, {{ longitude }}</text>
  83. </view>
  84. </view>
  85. </view>
  86. <view class="submit-btn" @click="confirmAddress">确定</view>
  87. </view>
  88. </template>
  89. <script>
  90. import navBar from "@/components/nav-bar/index.vue";
  91. import addr from "@/utils/addr.json";
  92. export default {
  93. data() {
  94. return {
  95. // 三级联动数据
  96. provinces: [], // 所有省份
  97. cities: [], // 选中省份对应的城市
  98. districts: [], // 选中城市对应的区域
  99. // 选中索引
  100. provinceIndex: null, // 选中的省份索引
  101. cityIndex: null, // 选中的城市索引
  102. districtIndex: null, // 选中的区域索引
  103. // 地址信息
  104. address: "", // 街道/楼宇(必填)
  105. addressDetail: '', // 楼层/单元室(选填)
  106. latitude: '', // 纬度(地图选点获取)
  107. longitude: '' // 经度(地图选点获取)
  108. };
  109. },
  110. components: {
  111. navBar,
  112. },
  113. // created() {
  114. // // 初始化省份数据
  115. // this.provinces = addr;
  116. // },
  117. onLoad(options) {
  118. this.provinces = addr;
  119. // 回显上一页传递的地址信息
  120. if (options.address) this.address = decodeURIComponent(options.address);
  121. if (options.addressDetail) this.addressDetail = decodeURIComponent(options.addressDetail);
  122. if (options.latitude) this.latitude = options.latitude;
  123. if (options.longitude) this.longitude = options.longitude;
  124. // 如果有省市区信息,进行回显
  125. if (options.province && options.city && options.district) {
  126. this.setAddressValue(
  127. decodeURIComponent(options.province),
  128. decodeURIComponent(options.city),
  129. decodeURIComponent(options.district)
  130. );
  131. }
  132. },
  133. methods: {
  134. // 省份选择改变
  135. onProvinceChange(e) {
  136. const index = e.detail.value;
  137. this.provinceIndex = index;
  138. // 重置城市和区域选择
  139. this.cityIndex = null;
  140. this.districtIndex = null;
  141. // 加载对应省份的城市
  142. this.cities = this.provinces[index].children || [];
  143. // 如果有城市数据,自动加载第一个城市的区域
  144. if (this.cities.length > 0) {
  145. this.districts = this.cities[0].children || [];
  146. } else {
  147. this.districts = [];
  148. }
  149. },
  150. // 城市选择改变
  151. onCityChange(e) {
  152. const index = e.detail.value;
  153. this.cityIndex = index;
  154. // 重置区域选择
  155. this.districtIndex = null;
  156. // 加载对应城市的区域
  157. this.districts = this.cities[index].children || [];
  158. },
  159. // 区域选择改变
  160. onDistrictChange(e) {
  161. this.districtIndex = e.detail.value;
  162. },
  163. // 根据省市区名称设置选择器值(用于回显)
  164. setAddressValue(provinceName, cityName, districtName) {
  165. // 查找省份索引
  166. const provinceIndex = this.provinces.findIndex(item => item.name === provinceName);
  167. if (provinceIndex !== -1) {
  168. this.provinceIndex = provinceIndex;
  169. this.cities = this.provinces[provinceIndex].children || [];
  170. // 查找城市索引
  171. const cityIndex = this.cities.findIndex(item => item.name === cityName);
  172. if (cityIndex !== -1) {
  173. this.cityIndex = cityIndex;
  174. this.districts = this.cities[cityIndex].children || [];
  175. // 查找区域索引
  176. const districtIndex = this.districts.findIndex(item => item.name === districtName);
  177. if (districtIndex !== -1) {
  178. this.districtIndex = districtIndex;
  179. }
  180. }
  181. }
  182. },
  183. // 打开地图选择地址
  184. openMapSelector() {
  185. uni.chooseLocation({
  186. success: (res) => {
  187. this.address = res.name || res.address;
  188. this.latitude = res.latitude;
  189. this.longitude = res.longitude;
  190. uni.showToast({
  191. title: '请确认并补充省/市/区信息',
  192. icon: 'none',
  193. duration: 2000
  194. });
  195. },
  196. fail: (err) => {
  197. console.error('地图选择失败:', err);
  198. if (err.errMsg.includes('auth')) {
  199. uni.showToast({ title: '请开启位置权限', icon: 'none' });
  200. }
  201. }
  202. });
  203. },
  204. // 确认地址并回传
  205. confirmAddress() {
  206. // 获取选中的省市区信息
  207. const province = this.provinceIndex !== null ? this.provinces[this.provinceIndex].name : '';
  208. const city = this.cityIndex !== null && this.cities.length > 0 ? this.cities[this.cityIndex].name : '';
  209. const district = this.districtIndex !== null && this.districts.length > 0 ? this.districts[this.districtIndex].name : '';
  210. // 验证必填项
  211. if (!province) {
  212. return uni.showToast({ title: '请选择省份', icon: 'none' });
  213. }
  214. if (!city) {
  215. return uni.showToast({ title: '请选择城市', icon: 'none' });
  216. }
  217. if (!district) {
  218. return uni.showToast({ title: '请选择区域', icon: 'none' });
  219. }
  220. if (!this.address.trim()) {
  221. return uni.showToast({ title: '请输入街道或楼宇名称', icon: 'none' });
  222. }
  223. // 合并完整地址信息
  224. const fullAddress = {
  225. province,
  226. city,
  227. district,
  228. address: this.address.trim(),
  229. addressDetail: this.addressDetail.trim(),
  230. latitude: this.latitude || '',
  231. longitude: this.longitude || '',
  232. fullText: `${province}${city}${district}${this.address.trim()}${this.addressDetail.trim() ? ' ' + this.addressDetail.trim() : ''}`
  233. };
  234. // 回传上一页
  235. uni.$emit('addressData', fullAddress);
  236. uni.navigateBack();
  237. }
  238. }
  239. };
  240. </script>
  241. <style lang="scss" scoped>
  242. /* 原有样式保持不变,添加以下选择器样式 */
  243. .picker-view {
  244. box-sizing: border-box;
  245. width: 100%;
  246. border: 2rpx solid #9ea1a8;
  247. border-radius: 100rpx;
  248. padding: 8rpx 36rpx;
  249. height: 96rpx;
  250. display: flex;
  251. align-items: center;
  252. font-size: 28rpx;
  253. color: #333;
  254. }
  255. picker {
  256. width: 100%;
  257. }
  258. /* 禁用状态样式 */
  259. picker:disabled .picker-view {
  260. color: #999;
  261. background-color: #f5f5f5;
  262. }
  263. /* 其他原有样式保持不变 */
  264. .switch-roles {
  265. background-color: #fff;
  266. position: absolute;
  267. left: 0;
  268. right: 0;
  269. top: 0;
  270. bottom: 0;
  271. display: flex;
  272. flex-direction: column;
  273. .roles-content {
  274. width: 100%;
  275. flex: 1;
  276. overflow-y: auto;
  277. .content {
  278. padding: 40rpx;
  279. box-sizing: border-box;
  280. .map-selector {
  281. display: flex;
  282. align-items: center;
  283. padding: 20rpx 24rpx;
  284. background-color: #f5f7fa;
  285. border-radius: 16rpx;
  286. margin-bottom: 30rpx;
  287. color: #007AFF;
  288. font-size: 28rpx;
  289. cursor: pointer;
  290. transition: background-color 0.2s;
  291. &:active {
  292. background-color: #e8ebf0;
  293. }
  294. }
  295. .location-info {
  296. margin-top: 10rpx;
  297. font-size: 24rpx;
  298. color: #999;
  299. padding: 10rpx 0;
  300. }
  301. .required {
  302. color: #ff4d4f; /* 红色必填标记 */
  303. font-size: 32rpx;
  304. margin-left: 4rpx;
  305. }
  306. }
  307. }
  308. .submit-btn {
  309. margin: 60rpx 20rpx;
  310. height: 96rpx;
  311. border-radius: 999px;
  312. background: linear-gradient(90deg, #0d27f7, #13c1ea);
  313. color: #fff;
  314. font-size: 32rpx;
  315. display: flex;
  316. justify-content: center;
  317. align-items: center;
  318. transition: all 0.2s ease;
  319. &:active {
  320. transform: scale(0.98);
  321. opacity: 0.9;
  322. }
  323. }
  324. }
  325. .custom-input {
  326. box-sizing: border-box;
  327. border: 2rpx solid #9ea1a8;
  328. border-radius: 100rpx;
  329. padding: 8rpx 36rpx;
  330. height: 96rpx;
  331. }
  332. ::v-deep .u-input__prefix {
  333. margin-right: 24rpx !important;
  334. }
  335. ::v-deep .u-input input {
  336. padding-left: 32rpx;
  337. }
  338. .form-item {
  339. margin-bottom: 26rpx;
  340. width: 100%;
  341. }
  342. .item-label {
  343. color: #121a2c;
  344. font-size: 32rpx;
  345. padding: 10rpx 0;
  346. display: flex;
  347. align-items: center;
  348. }
  349. ::v-deep .u-input input:focus {
  350. border-color: #007AFF !important;
  351. outline: none;
  352. }
  353. ::v-deep .u-input {
  354. text-align: left !important;
  355. }
  356. </style>