index.vue 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. <template>
  2. <view class="plate-page">
  3. <uv-navbar title="我的车辆" bgColor="#C6171E" leftIconColor="#FFFFFF" :titleStyle="{ color: '#FFFFFF' }" :autoBack="true" :placeholder="true"></uv-navbar>
  4. <!-- 车牌输入区域 -->
  5. <view class="plate-input-section">
  6. <view class="plate-input-header">
  7. <text class="plate-label">车牌号码:</text>
  8. <view class="default-checkbox">
  9. <uv-checkbox v-model="isDefault" shape="circle"></uv-checkbox>
  10. <text class="default-text">设为默认车辆</text>
  11. </view>
  12. </view>
  13. <view class="plate-input-hint">
  14. <text>添加车牌后部分站点可享停车减免权益,临牌减免权益请查看站点详细减免规则</text>
  15. </view>
  16. <!-- 车牌输入框 -->
  17. <view class="plate-input-container">
  18. <!-- 省份输入 -->
  19. <input class="plate-item plate-region"
  20. v-model="plateChars[0]"
  21. maxlength="1"
  22. type="text"
  23. inputmode="none"
  24. readonly
  25. @focus="onPlateFocus(0); $event.preventDefault();"
  26. @click="toggleRegionPicker"
  27. @touchstart.prevent="toggleRegionPicker"/>
  28. <!-- 城市输入 -->
  29. <input class="plate-item plate-region"
  30. v-model="plateChars[1]"
  31. maxlength="1"
  32. type="text"
  33. inputmode="text"
  34. @input="onPlateInput"
  35. @focus="onPlateFocus(1)"/>
  36. <!-- 分隔符 -->
  37. <view class="plate-divider">
  38. <text>·</text>
  39. </view>
  40. <!-- 字母数字输入框 -->
  41. <input v-for="(char, index) in plateChars.slice(2)"
  42. :key="index + 2"
  43. class="plate-item"
  44. :class="{ 'new-energy': index === 5 }"
  45. v-model="plateChars[index + 2]"
  46. maxlength="1"
  47. placeholder-class="placeholder"
  48. :placeholder="index === 5 ? '新能源' : ''"
  49. type="text"
  50. inputmode="text"
  51. @input="onPlateInput"
  52. @focus="onPlateFocus(index + 2)"/>
  53. </view>
  54. <!-- 保存按钮 -->
  55. <view class="save-btn-container">
  56. <uv-button type="primary" color="#C6171E" shape="circle" :disabled="!isPlateValid" @click="savePlate">保存</uv-button>
  57. </view>
  58. <!-- 地区选择器 -->
  59. <view v-if="showRegionPicker" class="region-picker-container">
  60. <view class="region-picker-header">
  61. <uv-button type="default" size="small" @click="toggleRegionPicker">完成</uv-button>
  62. </view>
  63. <view class="region-grid">
  64. <view v-for="region in regions" :key="region"
  65. class="region-item"
  66. @click="selectRegion(region)">
  67. <text>{{ region }}</text>
  68. </view>
  69. </view>
  70. </view>
  71. </view>
  72. </view>
  73. </template>
  74. <script setup>
  75. import {onLoad, onShow} from "@dcloudio/uni-app";
  76. import {ref, computed} from "vue";
  77. import {body} from "@/utils/https";
  78. const plateNo = ref("未绑定");
  79. const plateChars = ref(['粤', 'B', '', '', '', '', '', '']);
  80. const isDefault = ref(false);
  81. const currentInputIndex = ref(2);
  82. const showRegionPicker = ref(false);
  83. const regions = ['京', '津', '渝', '沪', '冀', '晋', '辽', '吉', '黑', '苏', '浙', '皖', '闽', '赣', '鲁', '豫', '鄂', '湘', '粤', '琼', '川', '贵', '云', '陕', '甘', '青', '蒙', '桂', '宁', '新', '藏', '使', '领', '警', '学', '港', '澳'];
  84. const isPlateValid = computed(() => {
  85. const validChars = plateChars.value.filter(char => char !== '');
  86. return validChars.length >= 7;
  87. });
  88. const onPlateInput = () => {
  89. for (let i = 0; i < plateChars.value.length; i++) {
  90. plateChars.value[i] = plateChars.value[i].toUpperCase();
  91. }
  92. };
  93. const onPlateFocus = (index) => {
  94. currentInputIndex.value = index;
  95. };
  96. const toggleRegionPicker = () => {
  97. showRegionPicker.value = !showRegionPicker.value;
  98. };
  99. const selectRegion = (region) => {
  100. plateChars.value[0] = region;
  101. toggleRegionPicker();
  102. if (plateChars.value[1] === '') {
  103. currentInputIndex.value = 1;
  104. }
  105. };
  106. const savePlate = () => {
  107. const newPlateNo = plateChars.value.filter(char => char !== '').join('');
  108. if (newPlateNo.length < 7) {
  109. uni.showToast({
  110. title: '请输入完整车牌号',
  111. icon: 'none'
  112. });
  113. return;
  114. }
  115. save({
  116. defaultPlateNo: newPlateNo,
  117. });
  118. };
  119. const save = (form) => {
  120. uni.showLoading({
  121. title: "保存中",
  122. });
  123. body(`/user/updateUser`, form).then(() => {
  124. uni.hideLoading();
  125. getApp().globalData.user.defaultPlateNo = form.defaultPlateNo;
  126. plateNo.value = form.defaultPlateNo;
  127. uni.showToast({
  128. icon: "success",
  129. title: "保存成功",
  130. });
  131. setTimeout(() => {
  132. uni.navigateBack();
  133. }, 1500);
  134. }).catch((err) => {
  135. uni.hideLoading();
  136. uni.showToast({
  137. title: "保存失败",
  138. icon: "none"
  139. });
  140. });
  141. };
  142. onLoad(() => {
  143. if (getApp().globalData.user) {
  144. const user = getApp().globalData.user;
  145. plateNo.value = user.defaultPlateNo || "未绑定";
  146. if (user.defaultPlateNo) {
  147. const plate = user.defaultPlateNo;
  148. plateChars.value = ['粤', 'B', '', '', '', '', '', ''];
  149. for (let i = 0; i < plate.length && i < plateChars.value.length; i++) {
  150. plateChars.value[i] = plate[i];
  151. }
  152. }
  153. }
  154. });
  155. onShow(() => {
  156. if (getApp().globalData.user) {
  157. const user = getApp().globalData.user;
  158. plateNo.value = user.defaultPlateNo || "未绑定";
  159. }
  160. });
  161. </script>
  162. <style lang="scss" scoped>
  163. .plate-page {
  164. background-color: $uni-bg-color-page;
  165. min-height: 100vh;
  166. box-sizing: border-box;
  167. padding-bottom: 200rpx;
  168. }
  169. .plate-input-section {
  170. background-color: $uni-bg-color-card;
  171. padding: 30rpx;
  172. box-sizing: border-box;
  173. margin-bottom: 20rpx;
  174. }
  175. .plate-input-header {
  176. display: flex;
  177. align-items: center;
  178. justify-content: space-between;
  179. margin-bottom: 10rpx;
  180. }
  181. .plate-label {
  182. font-size: 32rpx;
  183. font-weight: $uni-font-weight-semibold;
  184. color: $uni-text-color;
  185. }
  186. .default-checkbox {
  187. display: flex;
  188. align-items: center;
  189. gap: 8rpx;
  190. }
  191. .default-text {
  192. font-size: 28rpx;
  193. color: $uni-text-color-secondary;
  194. }
  195. .plate-input-hint {
  196. margin-bottom: 30rpx;
  197. }
  198. .plate-input-hint text {
  199. font-size: 26rpx;
  200. color: $uni-text-color-tertiary;
  201. line-height: 1.5;
  202. }
  203. .plate-input-container {
  204. display: flex;
  205. align-items: center;
  206. justify-content: center;
  207. margin-bottom: 40rpx;
  208. gap: 4rpx;
  209. }
  210. .plate-item {
  211. width: 64rpx;
  212. height: 88rpx;
  213. border: 2rpx solid $uni-border-color;
  214. border-radius: 4rpx;
  215. display: flex;
  216. align-items: center;
  217. justify-content: center;
  218. font-size: 36rpx;
  219. font-weight: $uni-font-weight-semibold;
  220. color: $uni-text-color;
  221. background-color: $uni-bg-color-card;
  222. transition: all 0.2s ease;
  223. box-sizing: border-box;
  224. text-align: center;
  225. }
  226. .plate-item.active {
  227. border-color: $uni-color-primary;
  228. background-color: rgba($uni-color-primary, 0.04);
  229. }
  230. .plate-item.new-energy {
  231. width: 80rpx;
  232. border-color: $uni-color-success;
  233. background-color: rgba($uni-color-success, 0.06);
  234. }
  235. .plate-item.new-energy .placeholder {
  236. color: $uni-color-success;
  237. font-size: 18rpx;
  238. text-align: center;
  239. line-height: 20rpx;
  240. }
  241. .plate-region {
  242. background-color: $uni-bg-color-page;
  243. cursor: pointer;
  244. }
  245. .placeholder {
  246. text-align: center;
  247. }
  248. .plate-divider {
  249. width: 24rpx;
  250. height: 88rpx;
  251. display: flex;
  252. align-items: center;
  253. justify-content: center;
  254. font-size: 32rpx;
  255. color: $uni-text-color-secondary;
  256. }
  257. .save-btn-container {
  258. margin-top: 20rpx;
  259. }
  260. .save-btn-container :deep(.uv-button) {
  261. height: 80rpx;
  262. font-size: 30rpx;
  263. border-radius: 40rpx;
  264. background-color: $uni-color-primary;
  265. }
  266. .save-btn-container :deep(.uv-button--disabled) {
  267. background-color: $uni-border-color;
  268. color: $uni-text-color-hint;
  269. }
  270. // 地区选择器
  271. .region-picker-container {
  272. position: fixed;
  273. bottom: 0;
  274. left: 0;
  275. right: 0;
  276. background-color: $uni-bg-color-card;
  277. border-radius: 20rpx 20rpx 0 0;
  278. padding: 15rpx;
  279. box-sizing: border-box;
  280. box-shadow: 0 -4rpx 12rpx rgba(0, 0, 0, 0.05);
  281. z-index: 1000;
  282. height: 38vh;
  283. overflow-y: hidden;
  284. }
  285. .region-picker-header {
  286. display: flex;
  287. justify-content: flex-end;
  288. margin-bottom: 5rpx;
  289. padding: 0 5rpx;
  290. }
  291. .region-picker-header :deep(.uv-button) {
  292. font-size: 30rpx;
  293. color: $uni-color-primary;
  294. background-color: transparent;
  295. border: none;
  296. padding: 5rpx 15rpx;
  297. }
  298. .region-grid {
  299. display: flex;
  300. flex-wrap: wrap;
  301. gap: 6rpx;
  302. padding: 5rpx;
  303. height: calc(38vh - 60rpx);
  304. overflow-y: hidden;
  305. justify-content: center;
  306. }
  307. .region-item {
  308. width: 85rpx;
  309. height: 65rpx;
  310. background-color: $uni-bg-color-page;
  311. border-radius: 8rpx;
  312. display: flex;
  313. align-items: center;
  314. justify-content: center;
  315. font-size: 40rpx;
  316. font-weight: $uni-font-weight-semibold;
  317. color: $uni-text-color;
  318. cursor: pointer;
  319. transition: all 0.2s ease;
  320. margin: 0;
  321. flex-shrink: 0;
  322. flex-basis: calc(16.66% - 8rpx);
  323. }
  324. .region-item:active {
  325. background-color: $uni-color-primary;
  326. color: $uni-text-color-inverse;
  327. transform: scale(0.95);
  328. }
  329. </style>