index.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. <template>
  2. <view class="page-container">
  3. <!-- 自定义导航栏 -->
  4. <view class="custom-navbar" :style="{paddingTop: statusBarHeight + 'px'}">
  5. <view class="navbar-content">
  6. <view class="navbar-title">
  7. <uv-icon name="account-fill" color="#FFFFFF" size="20"></uv-icon>
  8. <text class="title-text">我的</text>
  9. </view>
  10. </view>
  11. </view>
  12. <!-- 页面内容区域 -->
  13. <scroll-view class="content-scroll" scroll-y="true">
  14. <custom-service></custom-service>
  15. <!-- 用户信息与钱包合并卡片 -->
  16. <view class="user-wallet-card">
  17. <!-- 用户信息区域 -->
  18. <view class="user-section">
  19. <view class="user-avatar-wrapper">
  20. <image class="user-avatar" src='/static/iconfont/me.svg'></image>
  21. </view>
  22. <view class="user-info">
  23. <view class="user-phone">{{ user.mobilePhone || '未登录' }}</view>
  24. <view class="user-tip">欢迎使用Yeswash洗车</view>
  25. </view>
  26. </view>
  27. <!-- 分割线 -->
  28. <view class="divider"></view>
  29. <!-- 钱包信息区域 -->
  30. <view class="wallet-section" @click="toPage({path: '/pages-user/wallet/index'})">
  31. <view class="wallet-header">
  32. <view class="wallet-title">
  33. <uv-icon name="bag" size="18" color="#C6171E"></uv-icon>
  34. <text>我的钱包</text>
  35. </view>
  36. <uv-icon name="arrow-right" size="14" color="#C0C4CC"></uv-icon>
  37. </view>
  38. <view class="wallet-balance">
  39. <text class="balance-label">账户余额 (元)</text>
  40. <view class="balance-value">
  41. <text class="balance-symbol">¥</text>
  42. <text class="balance-amount">{{ ((user.balance || 0) / 100).toFixed(2) }}</text>
  43. </view>
  44. </view>
  45. <view class="wallet-actions">
  46. <view class="action-btn action-recharge" @click.stop="toPage({path: '/pages-user/wallet/recharge'})">
  47. <text>充值</text>
  48. </view>
  49. <view class="action-btn action-detail" @click.stop="toPage({path: '/pages-user/wallet/index'})">
  50. <text>明细</text>
  51. </view>
  52. </view>
  53. </view>
  54. </view>
  55. <!-- 功能菜单列表 -->
  56. <view class="menu-section">
  57. <view class="section-title">
  58. <view class="title-dot"></view>
  59. <text>功能服务</text>
  60. </view>
  61. <view class="menu-list">
  62. <view
  63. class="menu-item"
  64. v-for="(item, index) in menu"
  65. :key="index"
  66. @click="toPage(item)"
  67. >
  68. <view class="menu-left">
  69. <view class="menu-icon-box">
  70. <image class="menu-icon" :src="item.icon" mode="aspectFit"></image>
  71. </view>
  72. <text class="menu-text">{{ item.title }}</text>
  73. </view>
  74. <uv-icon name="arrow-right" size="16" color="#C0C4CC"></uv-icon>
  75. </view>
  76. </view>
  77. </view>
  78. <!-- 退出登录按钮 -->
  79. <view class="logout-wrapper" v-if="isLogin">
  80. <view class="logout-btn" @click="logoutUser">
  81. <uv-icon name="error-circle" color="#DD524D" size="18"></uv-icon>
  82. <text>退出登录</text>
  83. </view>
  84. </view>
  85. <!-- 底部占位 -->
  86. <view style="height: 140rpx;"></view>
  87. </scroll-view>
  88. <!-- 底部导航栏 -->
  89. <tab-bar :index="2"/>
  90. </view>
  91. </template>
  92. <script setup lang="ts">
  93. import {onHide, onLoad, onShow} from "@dcloudio/uni-app";
  94. import {computed, ref} from "vue";
  95. import TabBar from "@/components/tab-bar/index.vue";
  96. import LoginBar from "@/components/login-bar/index.vue";
  97. import {checkLogin, clearToken, loadUserInfo} from "@/utils/auth"
  98. import {get} from "@/utils/https";
  99. import {getServicePhone} from "@/utils/common";
  100. const statusBarHeight = ref(0)
  101. const user = ref<any>({
  102. id: 0,
  103. avatar: "",
  104. mobilePhoneFormat: '',
  105. balance: 0,
  106. });
  107. const isLogin = ref(false)
  108. const service = ref("15012341234");
  109. const menu = ref([
  110. {
  111. title: "绑定车辆",
  112. path: "/pages-user/profile/index",
  113. icon: '/static/user/profile.png'
  114. },
  115. {
  116. title: "我的订单",
  117. path: "/pages-order/list/index",
  118. icon: '/static/user/faq.png'
  119. },
  120. {
  121. title: "联系我们",
  122. path: "/pages-user/contact/index",
  123. icon: '/static/user/contact.png'
  124. },
  125. {
  126. title: "洗车指导",
  127. path: "/pages-user/faq/index",
  128. icon: '/static/iconfont/default/guide.svg'
  129. },
  130. {
  131. title: "纠错上报",
  132. path: "/pages-user/feedback/index",
  133. icon: '/static/user/feedback.png'
  134. },
  135. ]);
  136. const toPage = (item: any) => {
  137. checkLogin().then(() => {
  138. let {title, path} = item;
  139. let servicePhone = getServicePhone();
  140. if (path.includes('contact')) {
  141. uni.makePhoneCall({
  142. phoneNumber: servicePhone,
  143. fail: (error) => {
  144. }
  145. });
  146. return;
  147. }
  148. uni.navigateTo({
  149. url: item.path,
  150. });
  151. }).catch(e => {
  152. console.error("handleMenuClick 校验,跳转登录页", e)
  153. uni.navigateTo({
  154. url: '/pages-user/login/index'
  155. })
  156. })
  157. };
  158. const loginListen = () => {
  159. }
  160. const logoutUser = () => {
  161. uni.showModal({
  162. title: "温馨提示",
  163. content: "确定退出登录吗?",
  164. confirmColor: "#C6171E",
  165. confirmText: "确定退出",
  166. cancelText: "手滑了",
  167. success: (res) => {
  168. if (res.confirm) {
  169. uni.showLoading({
  170. title: "退出中",
  171. });
  172. get(`/user/logout`).then(() => {
  173. uni.hideLoading();
  174. uni.showToast({
  175. icon: "success",
  176. title: "已退出",
  177. });
  178. isLogin.value = false;
  179. getApp<any>().globalData.user = {};
  180. getApp<any>().globalData.manualLogout = true;
  181. clearToken();
  182. setTimeout(() => {
  183. uni.exitMiniProgram()
  184. }, 1500);
  185. })
  186. }
  187. },
  188. });
  189. };
  190. onLoad(() => {
  191. const systemInfo = uni.getSystemInfoSync();
  192. statusBarHeight.value = systemInfo.statusBarHeight || 0;
  193. });
  194. const addListener = () => {
  195. uni.$on('login', function (data) {
  196. isLogin.value = data.isLogin;
  197. if (data.isLogin) {
  198. user.value = getApp<any>().globalData.user;
  199. }
  200. })
  201. uni.$on('logout', function (data) {
  202. isLogin.value = false;
  203. user.value = {}
  204. })
  205. }
  206. const removeListener = () => {
  207. uni.$off('logout');
  208. uni.$off('login');
  209. }
  210. onShow(() => {
  211. let gd = getApp<any>().globalData;
  212. if (gd.refresh) {
  213. if (gd.user?.id) {
  214. refreshUserProfile()
  215. }
  216. } else {
  217. if (gd.user?.id) {
  218. if (new Date().getTime() - new Date(gd.user.lastLoginTime).getTime() > 300 * 1000) {
  219. refreshUserProfile()
  220. } else {
  221. isLogin.value = true;
  222. user.value = gd.user;
  223. }
  224. }
  225. }
  226. let currentPages = getCurrentPages();
  227. if (currentPages.length > 1) {
  228. let lastPage = currentPages[currentPages.length - 2]
  229. }
  230. addListener();
  231. });
  232. const refreshUserProfile = () => {
  233. isLogin.value = true;
  234. loadUserInfo().then(val => {
  235. if (val && val.id) {
  236. user.value = val;
  237. }
  238. getApp<any>().globalData.refresh = false;
  239. })
  240. }
  241. onHide(() => {
  242. removeListener();
  243. })
  244. </script>
  245. <style lang="scss" scoped>
  246. page {
  247. background: $uni-bg-color-page;
  248. }
  249. .page-container {
  250. width: 100vw;
  251. height: 100vh;
  252. background: linear-gradient(180deg, $uni-color-primary 0%, $uni-color-primary-light 25%, $uni-bg-color-page 45%);
  253. display: flex;
  254. flex-direction: column;
  255. }
  256. // 自定义导航栏
  257. .custom-navbar {
  258. position: relative;
  259. z-index: 100;
  260. .navbar-content {
  261. height: 88rpx;
  262. display: flex;
  263. align-items: center;
  264. justify-content: center;
  265. .navbar-title {
  266. display: flex;
  267. align-items: center;
  268. gap: 12rpx;
  269. .title-text {
  270. color: $uni-text-color-inverse;
  271. font-size: 36rpx;
  272. font-weight: $uni-font-weight-semibold;
  273. letter-spacing: 2rpx;
  274. }
  275. }
  276. }
  277. }
  278. // 内容滚动区域
  279. .content-scroll {
  280. flex: 1;
  281. height: 100%;
  282. width: 100%;
  283. }
  284. // 用户信息与钱包合并卡片
  285. .user-wallet-card {
  286. margin: 30rpx 30rpx 24rpx;
  287. @include card-interactive(24rpx);
  288. overflow: hidden;
  289. // 用户信息区域
  290. .user-section {
  291. padding: 32rpx;
  292. display: flex;
  293. align-items: center;
  294. gap: 24rpx;
  295. .user-avatar-wrapper {
  296. flex-shrink: 0;
  297. .user-avatar {
  298. width: 88rpx;
  299. height: 88rpx;
  300. border-radius: 50%;
  301. background: rgba($uni-color-primary, 0.08);
  302. padding: 8rpx;
  303. box-sizing: border-box;
  304. }
  305. }
  306. .user-info {
  307. flex: 1;
  308. .user-phone {
  309. font-size: 32rpx;
  310. font-weight: $uni-font-weight-semibold;
  311. color: $uni-text-color;
  312. margin-bottom: 8rpx;
  313. }
  314. .user-tip {
  315. font-size: 24rpx;
  316. color: $uni-text-color-hint;
  317. }
  318. }
  319. }
  320. // 分割线
  321. .divider {
  322. height: 1rpx;
  323. background: $uni-border-color-light;
  324. margin: 0 32rpx;
  325. }
  326. // 钱包区域
  327. .wallet-section {
  328. padding: 28rpx 32rpx 24rpx;
  329. text-align: center;
  330. .wallet-header {
  331. display: flex;
  332. justify-content: space-between;
  333. align-items: center;
  334. margin-bottom: 24rpx;
  335. .wallet-title {
  336. display: flex;
  337. align-items: center;
  338. gap: 8rpx;
  339. font-size: 26rpx;
  340. font-weight: $uni-font-weight-semibold;
  341. color: $uni-text-color;
  342. }
  343. }
  344. .wallet-balance {
  345. margin-bottom: 28rpx;
  346. .balance-label {
  347. font-size: 24rpx;
  348. color: $uni-text-color-hint;
  349. margin-bottom: 12rpx;
  350. display: block;
  351. }
  352. .balance-value {
  353. display: flex;
  354. align-items: baseline;
  355. justify-content: center;
  356. gap: 6rpx;
  357. .balance-symbol {
  358. font-size: 36rpx;
  359. color: $uni-text-color-dark;
  360. font-weight: $uni-font-weight-semibold;
  361. }
  362. .balance-amount {
  363. font-size: 64rpx;
  364. color: $uni-text-color-dark;
  365. font-weight: $uni-font-weight-bold;
  366. line-height: 1;
  367. }
  368. }
  369. }
  370. .wallet-actions {
  371. display: flex;
  372. justify-content: center;
  373. gap: 20rpx;
  374. .action-btn {
  375. padding: 14rpx 48rpx;
  376. border-radius: 40rpx;
  377. font-size: 26rpx;
  378. font-weight: $uni-font-weight-medium;
  379. &.action-recharge {
  380. background: $uni-color-primary;
  381. color: #fff;
  382. }
  383. &.action-detail {
  384. background: rgba($uni-color-primary, 0.08);
  385. color: $uni-color-primary;
  386. }
  387. }
  388. }
  389. }
  390. }
  391. // 菜单区域
  392. .menu-section {
  393. margin: 0 30rpx 24rpx;
  394. .section-title {
  395. display: flex;
  396. align-items: center;
  397. gap: 12rpx;
  398. padding: 0 12rpx;
  399. margin-bottom: 20rpx;
  400. font-size: 28rpx;
  401. font-weight: $uni-font-weight-semibold;
  402. color: $uni-text-color;
  403. .title-dot {
  404. width: 10rpx;
  405. height: 10rpx;
  406. background: $uni-color-primary;
  407. border-radius: 50%;
  408. }
  409. }
  410. .menu-list {
  411. @include card-interactive(24rpx);
  412. overflow: hidden;
  413. }
  414. .menu-item {
  415. display: flex;
  416. justify-content: space-between;
  417. align-items: center;
  418. padding: 28rpx 32rpx;
  419. border-bottom: 1rpx solid $uni-border-color-light;
  420. &:last-child {
  421. border-bottom: none;
  422. }
  423. .menu-left {
  424. display: flex;
  425. align-items: center;
  426. gap: 24rpx;
  427. .menu-icon-box {
  428. width: 72rpx;
  429. height: 72rpx;
  430. background: rgba($uni-color-primary, 0.08);
  431. border-radius: 16rpx;
  432. display: flex;
  433. align-items: center;
  434. justify-content: center;
  435. .menu-icon {
  436. width: 42rpx;
  437. height: 42rpx;
  438. }
  439. }
  440. .menu-text {
  441. font-size: 28rpx;
  442. color: $uni-text-color;
  443. }
  444. }
  445. }
  446. }
  447. // 退出按钮
  448. .logout-wrapper {
  449. margin: 40rpx 30rpx;
  450. .logout-btn {
  451. display: flex;
  452. align-items: center;
  453. justify-content: center;
  454. gap: 12rpx;
  455. height: 88rpx;
  456. @include card-interactive(44rpx);
  457. font-size: 28rpx;
  458. color: $uni-color-error;
  459. font-weight: $uni-font-weight-medium;
  460. }
  461. }
  462. </style>