巴青农资商城

index.vue 8.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. <template>
  2. <view class="mine-page">
  3. <view class="mine-header" @click="onHeaderClick">
  4. <view class="mine-header__row">
  5. <image class="mine-header__avatar" :src="avatarUrl" mode="aspectFill" />
  6. <view class="mine-header__info">
  7. <text class="mine-header__name">{{ headerTitle }}</text>
  8. <text v-if="loggedIn && mobileText" class="mine-header__sub">{{ mobileText }}</text>
  9. <text v-else-if="!loggedIn" class="mine-header__sub">登录后管理资料与地址</text>
  10. </view>
  11. <u-icon v-if="loggedIn" name="arrow-right" color="#fff" size="18" class="mine-header__arrow" />
  12. </view>
  13. </view>
  14. <scroll-view class="mine-scroll" scroll-y :style="{ height: scrollHeight }">
  15. <view class="mine-body">
  16. <view v-if="!loggedIn" class="mine-guest">
  17. <view class="mine-guest__hero">
  18. <view class="mine-guest__icon-ring">
  19. <u-icon name="account" color="#2e7d32" size="44" />
  20. </view>
  21. <text class="mine-guest__title">登录后享受完整购物服务</text>
  22. <text class="mine-guest__tip">下单、加购、查订单,一站式农资采购</text>
  23. </view>
  24. <view class="mine-guest__btns">
  25. <button class="mine-btn-primary mine-guest__btn-login" @click="goLogin">立即登录</button>
  26. <view class="mine-guest__register" @click="goRegister">
  27. <text class="mine-guest__register-text">还没有账号?</text>
  28. <text class="mine-guest__register-link">注册会员</text>
  29. </view>
  30. </view>
  31. </view>
  32. <template v-else>
  33. <view class="mine-order-card">
  34. <view class="mine-order-head" @click="goOrderListAll">
  35. <text class="mine-order-head__title">我的订单</text>
  36. <view class="mine-order-head__all">
  37. <text>全部</text>
  38. <u-icon name="arrow-right" color="#ccc" size="14" />
  39. </view>
  40. </view>
  41. <view class="mine-order-shortcuts">
  42. <view
  43. v-for="item in orderShortcuts"
  44. :key="item.key"
  45. class="mine-order-shortcut"
  46. @click="onOrderShortcut(item.key)"
  47. >
  48. <view class="mine-order-shortcut__icon-wrap">
  49. <u-icon :name="item.icon" color="#2e7d32" size="26" />
  50. <text
  51. v-if="getOrderBadge(item.key) > 0"
  52. class="mine-order-shortcut__badge"
  53. >{{ formatBadge(getOrderBadge(item.key)) }}</text>
  54. </view>
  55. <text class="mine-order-shortcut__label">{{ item.label }}</text>
  56. </view>
  57. </view>
  58. </view>
  59. <view v-for="section in menuSections" :key="section.title" class="mine-card">
  60. <text class="mine-card__title">{{ section.title }}</text>
  61. <view
  62. v-for="item in section.items"
  63. :key="item.path"
  64. class="mine-menu-item"
  65. @click="goPage(item.path)"
  66. >
  67. <u-icon :name="item.icon" color="#5c5652" size="22" />
  68. <text class="mine-menu-item__label">{{ item.label }}</text>
  69. <text
  70. v-if="item.showUnreadBadge && messageUnreadCount > 0"
  71. class="mine-menu-badge"
  72. >{{ formatMessageBadge(messageUnreadCount) }}</text>
  73. <u-icon name="arrow-right" color="#ccc" size="16" />
  74. </view>
  75. </view>
  76. <view class="mine-logout">
  77. <button class="mine-btn-outline" @click="handleLogout">退出登录</button>
  78. </view>
  79. </template>
  80. </view>
  81. </scroll-view>
  82. </view>
  83. </template>
  84. <script setup>
  85. import { ref, computed, onMounted } from 'vue'
  86. import { onShow } from '@dcloudio/uni-app'
  87. import { getToken } from '@/utils/auth'
  88. import { useUserStore } from '@/store/user'
  89. import { navigateMinePage } from '@/utils/mineNav'
  90. import { getOrderBadges } from '@/api/order'
  91. import { getMessageUnreadCount } from '@/api/message'
  92. import { formatMessageBadge } from '@/utils/messageDisplay'
  93. import { ORDER_MINE_SHORTCUTS, ORDER_TAB } from '@/constants/order'
  94. import { goOrderList, goAftersaleList } from '@/utils/orderNav'
  95. import {
  96. PAGE_LOGIN,
  97. PAGE_REGISTER,
  98. PAGE_PROFILE,
  99. PAGE_PASSWORD,
  100. PAGE_ADDRESS_LIST,
  101. PAGE_ENTRY_APPLY,
  102. PAGE_ENTRY_LIST,
  103. PAGE_SHOP_FOLLOW_LIST,
  104. PAGE_MESSAGE_LIST,
  105. PAGE_MESSAGE_DETAIL,
  106. PAGE_ABOUT_MALL
  107. } from '@/utils/pageRoute'
  108. const loggedIn = ref(false)
  109. const displayName = ref('点击登录')
  110. const mobileText = ref('')
  111. const avatarUrl = ref('/static/logo.png')
  112. const userStore = useUserStore()
  113. const orderShortcuts = ORDER_MINE_SHORTCUTS
  114. const orderBadges = ref({
  115. pendingPayCount: 0,
  116. pendingShipCount: 0,
  117. pendingReceiveCount: 0,
  118. aftersaleInProgressCount: 0
  119. })
  120. const messageUnreadCount = ref(0)
  121. const scrollHeight = ref('500px')
  122. const headerTitle = computed(() => {
  123. if (!loggedIn.value) return '未登录'
  124. return displayName.value
  125. })
  126. /** 我的服务菜单(对齐功能需求 §3) */
  127. const menuSections = [
  128. {
  129. title: '消息中心',
  130. items: [
  131. {
  132. label: '站内消息',
  133. path: PAGE_MESSAGE_LIST,
  134. icon: 'bell',
  135. showUnreadBadge: true
  136. }
  137. ]
  138. },
  139. {
  140. title: '账号管理',
  141. items: [
  142. { label: '编辑个人资料', path: PAGE_PROFILE, icon: 'account' },
  143. { label: '修改密码', path: PAGE_PASSWORD, icon: 'lock' }
  144. ]
  145. },
  146. {
  147. title: '收货地址',
  148. items: [{ label: '收货地址', path: PAGE_ADDRESS_LIST, icon: 'map' }]
  149. },
  150. {
  151. title: '店铺关注',
  152. items: [{ label: '我的店铺关注', path: PAGE_SHOP_FOLLOW_LIST, icon: 'heart' }]
  153. },
  154. {
  155. title: '商家入驻',
  156. items: [
  157. { label: '我要入驻', path: PAGE_ENTRY_APPLY, icon: 'home' },
  158. { label: '我的入驻申请', path: PAGE_ENTRY_LIST, icon: 'list' }
  159. ]
  160. },
  161. {
  162. title: '关于',
  163. items: [{ label: '关于商城', path: PAGE_ABOUT_MALL, icon: 'info-circle' }]
  164. }
  165. ]
  166. function calcScrollHeight() {
  167. try {
  168. const sys = uni.getSystemInfoSync()
  169. const windowH = sys.windowHeight || 600
  170. const tabBarH = uni.upx2px(188)
  171. const headerH = uni.upx2px(232)
  172. const overlapH = uni.upx2px(40)
  173. scrollHeight.value = `${Math.max(windowH - tabBarH - headerH + overlapH, 200)}px`
  174. } catch (e) {
  175. scrollHeight.value = '400px'
  176. }
  177. }
  178. onMounted(() => {
  179. calcScrollHeight()
  180. })
  181. onShow(() => {
  182. calcScrollHeight()
  183. loggedIn.value = !!getToken()
  184. if (!loggedIn.value) {
  185. displayName.value = '点击登录'
  186. mobileText.value = ''
  187. avatarUrl.value = '/static/logo.png'
  188. return
  189. }
  190. refreshUser()
  191. loadOrderBadges()
  192. loadMessageUnread()
  193. })
  194. function loadMessageUnread() {
  195. getMessageUnreadCount()
  196. .then((res) => {
  197. messageUnreadCount.value = Number((res.data && res.data.unreadCount) || 0)
  198. })
  199. .catch(() => {
  200. messageUnreadCount.value = 0
  201. })
  202. }
  203. function loadOrderBadges() {
  204. getOrderBadges()
  205. .then((res) => {
  206. const data = res.data || {}
  207. orderBadges.value = {
  208. pendingPayCount: Number(data.pendingPayCount) || 0,
  209. pendingShipCount: Number(data.pendingShipCount) || 0,
  210. pendingReceiveCount: Number(data.pendingReceiveCount) || 0,
  211. aftersaleInProgressCount: Number(data.aftersaleInProgressCount) || 0
  212. }
  213. })
  214. .catch(() => {
  215. orderBadges.value = {
  216. pendingPayCount: 0,
  217. pendingShipCount: 0,
  218. pendingReceiveCount: 0,
  219. aftersaleInProgressCount: 0
  220. }
  221. })
  222. }
  223. function getOrderBadge(key) {
  224. const map = {
  225. [ORDER_TAB.PENDING_PAY]: orderBadges.value.pendingPayCount,
  226. [ORDER_TAB.PENDING_SHIP]: orderBadges.value.pendingShipCount,
  227. [ORDER_TAB.PENDING_RECEIVE]: orderBadges.value.pendingReceiveCount,
  228. AFTERSALE: orderBadges.value.aftersaleInProgressCount
  229. }
  230. return map[key] || 0
  231. }
  232. function formatBadge(n) {
  233. return n > 99 ? '99+' : String(n)
  234. }
  235. function goOrderListAll() {
  236. goOrderList(ORDER_TAB.ALL)
  237. }
  238. function onOrderShortcut(key) {
  239. if (key === 'AFTERSALE') {
  240. goAftersaleList('IN_PROGRESS')
  241. return
  242. }
  243. goOrderList(key)
  244. }
  245. function refreshUser() {
  246. displayName.value = userStore.displayName() || '会员'
  247. mobileText.value = userStore.state.mobile || ''
  248. avatarUrl.value = userStore.state.avatar || '/static/logo.png'
  249. if (!userStore.state.memberId) {
  250. userStore.fetchUserInfo().then(() => {
  251. displayName.value = userStore.displayName() || '会员'
  252. mobileText.value = userStore.state.mobile || ''
  253. avatarUrl.value = userStore.state.avatar || '/static/logo.png'
  254. })
  255. }
  256. }
  257. function onHeaderClick() {
  258. if (!loggedIn.value) {
  259. goLogin()
  260. return
  261. }
  262. navigateMinePage(PAGE_PROFILE)
  263. }
  264. function goPage(path) {
  265. navigateMinePage(path)
  266. }
  267. function goLogin() {
  268. uni.navigateTo({ url: PAGE_LOGIN })
  269. }
  270. function goRegister() {
  271. uni.navigateTo({ url: PAGE_REGISTER })
  272. }
  273. function handleLogout() {
  274. uni.showModal({
  275. title: '提示',
  276. content: '确定退出当前账号吗?',
  277. success: (res) => {
  278. if (res.confirm) {
  279. userStore.logOut().then(() => {
  280. loggedIn.value = false
  281. displayName.value = '点击登录'
  282. mobileText.value = ''
  283. uni.showToast({ title: '已退出', icon: 'none' })
  284. })
  285. }
  286. }
  287. })
  288. }
  289. </script>
  290. <style lang="scss" scoped>
  291. @import '@/styles/mine.scss';
  292. .mine-menu-item u-icon:first-child {
  293. margin-right: 16rpx;
  294. }
  295. .mine-menu-item {
  296. gap: 16rpx;
  297. }
  298. </style>