TabBar.vue 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. <template>
  2. <view class="placeholder safe-area" v-if="placeholder && fixed"></view>
  3. <view class="tab-bar-wrap safe-area" :style="[getTabBarWrapStyle]">
  4. <view class="tab-bar">
  5. <!-- 左侧:首页 -->
  6. <view class="menu" @click="go(menus[0].link)">
  7. <image v-if="getPath === menus[0].link" :src="getFileUrl(menus[0].img)" mode="aspectFill"></image>
  8. <image v-else :src="getFileUrl(menus[0].imgon)" mode="aspectFill"></image>
  9. <view class="text" :class="{ sele: getPath === menus[0].link }">{{ menus[0].title }}</view>
  10. </view>
  11. <!-- 中间:扫码点餐 - 点击先调起微信扫一扫 -->
  12. <view class="menu-center" @click="handleScanOrder">
  13. <view class="scan-btn">
  14. <image :src="getFileUrl(menus[1].img)" mode="aspectFill"></image>
  15. </view>
  16. <image :src="getFileUrl(menus[1].imgon)" mode="heightFix" class="qrT-img"></image>
  17. </view>
  18. <!-- 右侧:我的 -->
  19. <view class="menu" @click="go(menus[2].link)">
  20. <image v-if="getPath === menus[2].link" :src="getFileUrl(menus[2].img)" mode="aspectFill"></image>
  21. <image v-else :src="getFileUrl(menus[2].imgon)" mode="aspectFill"></image>
  22. <view class="text" :class="{ sele: getPath === menus[2].link }">{{ menus[2].title }}</view>
  23. </view>
  24. </view>
  25. </view>
  26. </template>
  27. <script setup>
  28. import { computed, unref } from 'vue';
  29. import { getFileUrl } from '@/utils/file.js';
  30. import { SCAN_QR_CACHE } from '@/settings/enums.js';
  31. import { parseQrScanResult, isScanEntryAllowed } from '@/utils/qrScene.js';
  32. import { syncM2GenericPricingStorage } from '@/utils/m2GenericApiPath.js';
  33. import { useUserStore } from '@/store/user.js';
  34. import { TOKEN } from '@/settings/enums.js';
  35. const menus = [
  36. { title: '首页', link: '/pages/index/index', img: 'img/tabbar/index1.png', imgon: 'img/tabbar/index2.png' },
  37. { title: '扫码点餐', link: '/pages/numberOfDiners/index', img: 'img/tabbar/qr2.png', imgon: 'img/tabbar/qr.png' },
  38. { title: '我的', link: '/pages/personal/index', img: 'img/tabbar/personal1.png', imgon: 'img/tabbar/personal2.png' }
  39. ];
  40. const props = defineProps({
  41. placeholder: { type: Boolean, default: false }, // 是否显示占位
  42. fixed: { type: Boolean, default: true } // 是否定位
  43. });
  44. const getTabBarWrapStyle = computed(() => ({
  45. position: props.fixed ? 'fixed' : 'relative'
  46. }));
  47. // 点击扫码点餐:未登录提示请登录;已登录扫码后跳转点餐页(m=1 或无 m 为美食,走 dining)
  48. function handleScanOrder() {
  49. const userStore = useUserStore();
  50. const token = userStore.getToken || uni.getStorageSync(TOKEN) || '';
  51. if (!token) {
  52. uni.showToast({ title: '请登录', icon: 'none' });
  53. return;
  54. }
  55. uni.scanCode({
  56. scanType: ['wxCode', 'qrCode', 'barCode'],
  57. success: (res) => {
  58. const result = (res?.path || res?.result || '').trim();
  59. const { storeId, tableId, m } = parseQrScanResult(result);
  60. const payload = { raw: result, storeId, tableId, m };
  61. uni.setStorageSync(SCAN_QR_CACHE, JSON.stringify(payload));
  62. syncM2GenericPricingStorage(m);
  63. if (!isScanEntryAllowed(m)) {
  64. uni.showToast({ title: '请扫描正确的点餐二维码', icon: 'none' });
  65. return;
  66. }
  67. if (storeId) uni.setStorageSync('currentStoreId', storeId);
  68. if (tableId) uni.setStorageSync('currentTableId', tableId);
  69. const diners = uni.getStorageSync('currentDiners') || '1';
  70. uni.setStorageSync('currentDiners', diners);
  71. if (!tableId) {
  72. uni.showToast({ title: '未识别到桌号,请扫描正确的桌号二维码', icon: 'none' });
  73. return;
  74. }
  75. uni.reLaunch({
  76. url: `/pages/orderFood/index?tableid=${encodeURIComponent(tableId)}&diners=${encodeURIComponent(diners)}`
  77. });
  78. },
  79. fail: (err) => {
  80. const msg = err?.errMsg || '';
  81. if (msg && !msg.includes('cancel')) {
  82. if (msg.includes('auth') || msg.includes('camera') || msg.includes('scope')) {
  83. uni.showModal({
  84. title: '提示',
  85. content: '需要相机权限才能扫码,请在设置中开启',
  86. confirmText: '去设置',
  87. success: (res) => {
  88. if (res.confirm) uni.openSetting();
  89. }
  90. });
  91. } else {
  92. uni.showToast({ title: msg || '扫码失败', icon: 'none' });
  93. }
  94. }
  95. }
  96. });
  97. }
  98. // 底部 tabBar 跳转(需在 pages.json 中配置 tabBar.list)
  99. function go(url) {
  100. if (url === unref(getPath)) return;
  101. uni.switchTab({ url });
  102. }
  103. const getPath = computed(() => {
  104. const pages = getCurrentPages(); // 获取路由栈
  105. console.log('/' + pages[pages.length - 1].route);
  106. return '/' + pages[pages.length - 1].route;
  107. });
  108. </script>
  109. <style lang="scss" scoped>
  110. .placeholder {
  111. height: 140rpx;
  112. box-sizing: content-box;
  113. }
  114. .tab-bar-wrap {
  115. position: fixed;
  116. left: 0;
  117. right: 0;
  118. bottom: 0;
  119. z-index: 99;
  120. background-color: #ffffff;
  121. box-shadow: 0 -2rpx 16rpx 0 rgba(0, 0, 0, 0.08);
  122. .tab-bar {
  123. // padding: 16rpx 0;
  124. // padding-bottom: calc(16rpx + env(safe-area-inset-bottom));
  125. display: flex;
  126. align-items: flex-end;
  127. justify-content: space-around;
  128. position: relative;
  129. padding-top: 20rpx;
  130. .menu {
  131. display: flex;
  132. flex-direction: column;
  133. align-items: center;
  134. justify-content: center;
  135. flex: 1;
  136. padding: 0 40rpx;
  137. padding-bottom: 0;
  138. transition: all 0.3s ease;
  139. image {
  140. width: 48rpx;
  141. height: 48rpx;
  142. transition: transform 0.3s ease;
  143. }
  144. .text {
  145. font-size: 22rpx;
  146. color: #999999;
  147. margin-top: 8rpx;
  148. transition: all 0.3s ease;
  149. font-weight: 400;
  150. }
  151. .text.sele {
  152. color: #333333;
  153. font-weight: 500;
  154. }
  155. &:active {
  156. opacity: 0.7;
  157. transform: scale(0.95);
  158. }
  159. }
  160. .menu-center {
  161. display: flex;
  162. flex-direction: column;
  163. align-items: center;
  164. justify-content: center;
  165. position: absolute;
  166. bottom: -20rpx;
  167. margin: 0 40rpx;
  168. // transform: translateY(-35rpx);
  169. transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
  170. background-color: #fff;
  171. border-radius: 50%;
  172. padding: 20rpx;
  173. .scan-btn {
  174. width: 110rpx;
  175. height: 110rpx;
  176. border-radius: 50%;
  177. display: flex;
  178. align-items: center;
  179. justify-content: center;
  180. margin-bottom: 6rpx;
  181. position: relative;
  182. transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
  183. background: linear-gradient(35deg, #FCB73F 0%, #FC733D 100%);
  184. box-shadow: 0rpx 0rpx 11rpx 0rpx rgba(0, 0, 0, 0.16);
  185. image {
  186. width: 42rpx;
  187. height: 42rpx;
  188. filter: brightness(0) invert(1);
  189. transition: transform 0.3s ease;
  190. margin-bottom: 20rpx;
  191. }
  192. }
  193. .qrT-img {
  194. width: 85rpx;
  195. height: 22rpx;
  196. position: absolute;
  197. bottom: 36rpx;
  198. left: 50%;
  199. transform: translateX(-50%);
  200. }
  201. .text-center {
  202. font-size: 22rpx;
  203. color: #ff8844;
  204. margin-top: 4rpx;
  205. font-weight: 500;
  206. transition: all 0.3s ease;
  207. }
  208. &:active {
  209. transform: translateY(-35rpx) scale(0.92);
  210. .scan-btn {
  211. box-shadow: 0 4rpx 16rpx 0 rgba(255, 136, 68, 0.3),
  212. 0 1rpx 4rpx 0 rgba(255, 136, 68, 0.15);
  213. image {
  214. transform: scale(0.9);
  215. }
  216. }
  217. }
  218. }
  219. }
  220. }
  221. </style>