uni-app微信小程序动态切换tabBar,根据不同用户角色展示不同的tabBar

5,998 阅读3分钟

前言

在UniApp的开发小程序过程中,为了针对不同角色用户登录后的个性化需求。通过动态权限配置机制,能够根据用户的角色展示不同的TabBar。此项目是通过Uni-App命令行的方式搭建的Vue3+Vite+Ts+Pinia+Uni-ui的小程序项目

最终效果

  • 1、司机角色: 在这里插入图片描述

  • 2、供应商角色: 在这里插入图片描述

  • 3、司机且供应商角色: 在这里插入图片描述

目前常规的实现方式,大多数都是封装一个tabbar组件,在需要显示tabbar的页面添加这个组件,在根据一个选中的index值来切换选中效果。

而我的实现方式:把所有有tabbar的页面全部引入在一个tabbarPage页面,根据角色userType,来动态显示页面

实现思路

1、常规登录:通过微信登录获取code 2、根据code获取openId 3、根据openId获取token,若token存在表:此用户已经登陆/绑定过,则根据token获取用户信息,根据角色直接进入项目页面;若token不存在,则跳转到登录页面 4、登录成功后,调用用户信息接口,根据角色直接进入项目页面

1、以下是封装了一个useLogin的hooks


interface ApiResponse<T = any> {
  success: boolean;
  code: number;
  msg: string;
  data: T;
}

interface OpenIdResponse {
  openId: string;
  unionId: string;
}

interface UserInfoResponse {
  roles: { roleKey: string }[];
  [key: string]: any;
}

export const useLogin = () => {
  const { proxy } = getCurrentInstance() as { proxy: any };

  // 常规登录
  const judgmentLogin = (pathName = "", queryParams = ""): void => {
    uni.login({
      provider: "weixin", // 使用微信登录
      success: async (loginRes) => {
        console.log("loginRes----", loginRes.code);
        try {
          const res: ApiResponse<OpenIdResponse> = await proxy.$api.getOpenid({ code: loginRes.code });
          if (res.success) {
            console.log("微信信息----", res.data);
            uni.setStorageSync("openId", res.data.openId);
            uni.setStorageSync("unionId", res.data.unionId);
            await openidLogin(res.data.openId, res.data.unionId, pathName, queryParams);
          } else {
            handleError(res.msg);
          }
        } catch (error) {
          handleError("获取 OpenID 失败!");
        }
      },
      fail: () => {
        handleError("微信登录失败!");
      },
    });
  };

  // 登录过的用户再次进入,根据 openId 获取 token
  const openidLogin = async (openId: string, unionId: string, pathName: string, queryParams: string): Promise<void> => {
    try {
      const res: ApiResponse<string> = await proxy.$api.openIdLogin({ openId, unionId });
      console.log("openIdLogin----", res);
      if (res.success) {
        switch (res.code) {
          case 200:
            if (res.data) {
              uni.setStorageSync("token", res.data);
              if (await userInfo()) {
                handleRedirect(pathName, queryParams);
              }
            } else {
              handleError(res.msg);
            }
            break;
          case 10012:
            handleOpenIdNotFound(pathName, queryParams);
            break;
          default:
            handleError(res.msg);
        }
      } else {
        handleError("无法登录!");
      }
    } catch (error) {
      handleError("登录失败!");
    }
  };

  // 获取用户信息
  const userInfo = async (): Promise<boolean> => {
    const openId = uni.getStorageSync("openId");
    const unionId = uni.getStorageSync("unionId");
    try {
      const res: ApiResponse<UserInfoResponse> = await proxy.$api.getUserInfo({ openId, unionId });
      if (res.success && res.data) {
        console.log("获取登录用户信息", res.data);
        uni.setStorageSync("userInfo", JSON.stringify(res.data));
        const userTypeList = ["scm_driver", "scm_supplier", "supplier_and_driver"];
        const userType = res.data.roles?.find((role) => userTypeList.includes(role.roleKey))?.roleKey;

        if (userType) {
          uni.setStorageSync("userType", userType);
          return true;
        } else {
          showToastAndRedirect("当前用户角色没有权限!", "/pages/error/error?title=当前用户角色没有权限!");
        }
      } else {
        handleError("用户角色权限获取失败!");
      }
    } catch (error) {
      handleError("获取用户信息失败!");
    }
    return false;
  };

  // 处理跳转逻辑
  const handleRedirect = (pathName: string, queryParams: string): void => {
    console.log("外部链接跳转页面和参数----", pathName, queryParams);
    if (pathName) {
      if (pathName === "/pages/login/login" || pathName === "/pages/register/register") {
        uni.reLaunch({ url: "/pages/tabbarPage/tabbarPage" });
      } else {
        uni.reLaunch({ url: `${pathName}?${queryParams}` });
      }
    } else {
      uni.reLaunch({ url: "/pages/tabbarPage/tabbarPage" });
    }
  };

  // 处理 OpenID 不存在的情况
  const handleOpenIdNotFound = (pathName: string, queryParams: string): void => {
    console.log("外部链接跳转页面和参数----openid不存在", pathName, queryParams);
    if (pathName === "/pages/login/login" || pathName === "/pages/register/register") {
      uni.reLaunch({ url: `${pathName}?${queryParams}` });
    } else {
      uni.navigateTo({ url: "/pages/login/login" });
    }
  };

  // 显示错误信息并跳转
  const handleError = (message: string): void => {
    console.error(message);
    uni.navigateTo({ url: `/pages/error/error?title=${message}` });
  };

  // 显示提示并跳转
  const showToastAndRedirect = (message: string, redirectUrl: string): void => {
    uni.showToast({ icon: "none", title: message });
    setTimeout(() => {
      uni.navigateTo({ url: redirectUrl });
    }, 500);
  };

  return {
    judgmentLogin,
    userInfo,
  };
};

2、修改page.json中的tabBar

"tabBar": {
		"color": "#a6b9cb",
		"selectedColor": "#355db4",
		"list": [
			{
				"pagePath": "pages/supplierMyorder/supplierMyorder"
			},
			{
				"pagePath": "pages/driverMyorder/driverMyorder"
			},
			{
				"pagePath": "pages/mycar/mycar"
			},
			{
				"pagePath": "pages/driverPersonal/driverPersonal"
			}
		]
	},

3、关键页面tabbarPage.vue

<template>
  <div class="tabbar_page flex-box flex-col">
    <!-- 根据 userType 和 active 显示对应的组件 -->
    <div class="page_wrap" v-show="isActive('supplierMyorder')">
      <supplier-myorder
        ref="supplierMyorder"
        :show="isActive('supplierMyorder')"
      />
    </div>
    <div class="page_wrap" v-show="isActive('supplierPersonal')">
      <supplier-personal
        ref="supplierPersonal"
        :show="isActive('supplierPersonal')"
      />
    </div>
    <div class="page_wrap" v-show="isActive('driverMyorder')">
      <driver-myorder ref="driverMyorder" :show="isActive('driverMyorder')" />
    </div>
    <div class="page_wrap" v-show="isActive('mycar')">
      <mycar ref="mycar" :show="isActive('mycar')" />
    </div>
    <div class="page_wrap" v-show="isActive('driverPersonal')">
      <driver-personal
        ref="driverPersonal"
        :show="isActive('driverPersonal')"
      />
    </div>

    <!-- 底部 TabBar -->
    <view class="tab">
      <view
        v-for="(item, index) in tabbarOptions"
        :key="index"
        class="tab-item"
        @tap="switchTab(item, index)"
      >
        <image
          class="tab_img"
          :src="currentIndex === index ? item.selectedIconPath : item.iconPath"
        ></image>
        <view
          class="tab_text"
          :style="{ color: currentIndex === index ? selectedColor : color }"
        >
          {{ item.text }}
        </view>
      </view>
    </view>
  </div>
</template>

<script lang="ts" setup>
import supplierMyorder from '@/pages/supplierMyorder/supplierMyorder.vue'
import supplierPersonal from '@/pages/supplierPersonal/supplierPersonal.vue'
import driverMyorder from '@/pages/driverMyorder/driverMyorder.vue'
import mycar from '@/pages/mycar/mycar.vue'
import driverPersonal from '@/pages/driverPersonal/driverPersonal.vue'

// 定义类型
interface TabbarOption {
  name: string
  pagePath: string
  iconPath: string
  selectedIconPath: string
  text: string
}

const color = ref<string>('#666666')
const selectedColor = ref<string>('#355db4')
const currentIndex = ref<number>(0)
const active = ref<string>('')

// 切换 Tab 方法
const switchTab = (item: TabbarOption, index: number): void => {
  currentIndex.value = index
  active.value = item.name
}

// 页面加载时初始化
onLoad((option: { index?: number; name?: string }) => {
  currentIndex.value = option.index || 0
  active.value = option.name || tabbarOptions.value[0]?.name || ''
})

// 页面显示时确保 active 和 currentIndex 有值
onShow(() => {
  active.value = active.value || tabbarOptions.value[0]?.name || ''
  currentIndex.value = currentIndex.value || 0
  console.log('userType----', userType.value)
})

// 从缓存中获取用户类型
const userType = computed<string>(() => {
  return uni.getStorageSync('userType') || ''
})

// 根据用户类型生成 TabBar 配置
const tabbarOptions = computed<TabbarOption[]>(() => {
  const commonOptions: TabbarOption[] = [
    {
      name: 'supplierMyorder',
      pagePath: '/pages/supplierMyorder/supplierMyorder',
      iconPath: '/static/tabbar/order.png',
      selectedIconPath: '/static/tabbar/order_active.png',
      text: '我的订单'
    },
    {
      name: 'driverMyorder',
      pagePath: '/pages/driverMyorder/driverMyorder',
      iconPath: '/static/tabbar/waybill.png',
      selectedIconPath: '/static/tabbar/waybill_active.png',
      text: '我的运单'
    },
    {
      name: 'mycar',
      pagePath: '/pages/mycar/mycar',
      iconPath: '/static/tabbar/car.png',
      selectedIconPath: '/static/tabbar/car_active.png',
      text: '我的车辆'
    },
    {
      name: 'supplierPersonal',
      pagePath: '/pages/supplierPersonal/supplierPersonal',
      iconPath: '/static/tabbar/my.png',
      selectedIconPath: '/static/tabbar/my_active.png',
      text: '个人中心'
    },
    {
      name: 'driverPersonal',
      pagePath: '/pages/driverPersonal/driverPersonal',
      iconPath: '/static/tabbar/my.png',
      selectedIconPath: '/static/tabbar/my_active.png',
      text: '个人中心'
    }
  ]

  const userTypeOptions: Record<string, TabbarOption[]> = {
    scm_supplier: [commonOptions[0], commonOptions[3]],
    scm_driver: [commonOptions[1], commonOptions[2], commonOptions[4]],
    supplier_and_driver: [
      commonOptions[0],
      commonOptions[1],
      commonOptions[2],
      commonOptions[3]
    ]
  }

  return userTypeOptions[userType.value] || []
})

// 判断当前激活的页面
const isActive = (name: string): boolean => {
  return active.value === name
}
</script>

<style lang="scss" scoped>
.tabbar_page {
  height: 100%;
  .page_wrap {
    height: calc(100% - 84px);
    &.hidden {
      display: none;
    }
  }
  .tab {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    height: 100rpx;
    background: white;
    display: flex;
    justify-content: center;
    align-items: center;
    padding-bottom: env(safe-area-inset-bottom); // 适配iphoneX的底部
    border-top: 1px solid #eee;

    .tab-item {
      flex: 1;
      text-align: center;
      display: flex;
      justify-content: center;
      align-items: center;
      flex-direction: column;

      .tab_img {
        width: 45rpx;
        height: 45rpx;
      }

      .tab_text {
        font-size: 25rpx;
        margin: 9rpx 0;
      }
    }
  }
}
</style>


相关文章

基于ElementUi再次封装基础组件文档


Vue3+Vite+Ts+Pinia+Qiankun后台管理系统


vue3+ts基于Element-plus再次封装基础组件文档