You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue3+Pinia中调用ref对象的hasPermission函数报错求助

问题描述

在基于Vue3、TypeScript和Pinia的项目中,在_Layout.vue组件调用currentUser.value.hasPermission()时,出现错误:

Uncaught (in promise) TypeError: currentUser.value.hasPermission is not a function

核心疑问:是否是ref或Pinia存储的特性,导致无法调用存储对象上的函数?


相关代码

1. Pinia Store 代码 (@/stores/main-store.ts)

import { defineStore } from "pinia";
import type { IUser } from "@/lib/user";

export const useStore = defineStore({
  id: "main",
  state: () => ({
    currentUser: {} as IUser,
    currentTenant: null as string | null,
    theme: null as string | null,
  }),
});

2. 用户模型代码 (@/lib/user.ts)

import { getToken } from "@/lib/auth";

export interface IUser {
  id: string;
  tenants: IUserTenant[];
  hasPermission: (
    tenantIdentifier: string|null,
    permission: IUserPermission
  ) => boolean;
}

export interface IUserTenant {
  tenantIdentifier: string;
  tenantName: string;
  permissions: IUserPermission[];
}

export interface IUserPermission {
  permissionZone: PermissionZone;
  permissionType: PermissionType;
}

export enum PermissionZone {
  Me,
  Tenants,
  Users,
  Contacts,
}
export enum PermissionType {
  List,
  Read,
  Create,
  Update,
  Delete,
}

const API_EP = import.meta.env.VITE_API_ENDPOINT;
export class User implements IUser {

  public static async getCurrentUser(): Promise<IUser> {
    const token = await getToken();
    const response = await fetch(`${API_EP}/user`, {
      headers: {
        Authorization: `Bearer ${token}`,
      },
      mode: "cors",
    });

    if (response.ok) {
      return (await response.json()) as User;
    }

    // TODO Define Error response model and parse for message
    throw new Error("Unable to retrieve current user.");
  }

  public id!: string;
  public tenants: IUserTenant[] = [];
  public hasPermission(tenantIdentifier: string | null, permission: IUserPermission): boolean {
    return this.tenants.some(
      (t) =>
        t.tenantIdentifier === tenantIdentifier &&
        t.permissions.some(
          (p) =>
            p.permissionZone === permission.permissionZone &&
            p.permissionType === permission.permissionType
        )
    );
  }
}

3. Layout组件代码 (@/views/_Layout.vue 的 <script> 部分)

import { onMounted, ref } from "vue";
import { useStore } from "@/stores/main-store";
import { storeToRefs } from "pinia";
import { Navigation } from "@/lib/navigation";
import type { INavigationItem } from "@/lib/navigation";

const store = useStore();
const { currentUser, currentTenant } = storeToRefs(store);

const navItems = ref<INavigationItem[]>();
onMounted(async () => {
  navItems.value = Navigation.filter((i) =>
    currentUser.value.hasPermission(
      currentTenant.value,
      i.permission
    )
  );
});

补充:路由中currentUser的赋值逻辑 (@/router/index.ts)

import { createRouter, createWebHistory } from "vue-router";
import Layout from "@/views/_Layout.vue";
import { AuthenticationGuard } from "vue-auth0-plugin";
import { User } from "@/lib/user";
import { useStore } from "@/stores/main-store";

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      name: "default",
      path: "/",
      component: Layout,
      beforeEnter: async (to, from) => {
        // Handle login
        const authed = await AuthenticationGuard(to, from);

        useStore().$patch({
          currentUser: await User.getCurrentUser(),
        });

        if (authed && to.name === "default")
          await router.push({ name: "dashboard" });
        return authed;
      },
      children: [
        {
          name: "dashboard",
          path: "",
          component: () => import("@/views/DashboardPage.vue"),
        },
      ],
    },
  ],
});

export default router;

问题原因与解决方法

核心原因

不是ref或Pinia的特性导致的问题,而是API返回的JSON对象只是普通对象,没有继承User类的方法。你在User.getCurrentUser()里把response.json()断言为User类型,但实际上JSON解析出来的对象不会包含类的原型方法hasPermission,只是拥有相同属性的普通对象。

另外,Pinia的state是普通对象,即使你一开始把currentUser声明为IUser类型,当用普通对象替换后,它也不会有类方法。

解决方法

方法1:将API返回的普通对象转换为User实例

修改User.getCurrentUser()方法,把JSON返回的对象转换成User类的实例,这样就能继承hasPermission方法:

public static async getCurrentUser(): Promise<User> {
  const token = await getToken();
  const response = await fetch(`${API_EP}/user`, {
    headers: {
      Authorization: `Bearer ${token}`,
    },
    mode: "cors",
  });

  if (response.ok) {
    const userData = await response.json();
    // 创建User实例并赋值属性
    const user = new User();
    user.id = userData.id;
    user.tenants = userData.tenants;
    return user;
  }

  throw new Error("Unable to retrieve current user.");
}

方法2:把hasPermission改成独立函数(无需类实例)

如果不想用类实例,可以把权限判断逻辑抽成独立函数,不需要依赖User类的this:

// @/lib/user.ts 中添加独立函数
export function hasPermission(
  user: IUser,
  tenantIdentifier: string | null,
  permission: IUserPermission
): boolean {
  return user.tenants.some(
    (t) =>
      t.tenantIdentifier === tenantIdentifier &&
      t.permissions.some(
        (p) =>
          p.permissionZone === permission.permissionZone &&
          p.permissionType === permission.permissionType
      )
  );
}

然后在Layout组件中调用:

import { hasPermission } from "@/lib/user";

// ...

onMounted(async () => {
  navItems.value = Navigation.filter((i) =>
    hasPermission(currentUser.value, currentTenant.value, i.permission)
  );
});

方法3:在Pinia Store中添加action处理权限判断

也可以在Pinia Store里添加一个action来封装权限判断逻辑,避免直接在组件中依赖用户对象的方法:

// @/stores/main-store.ts
export const useStore = defineStore({
  id: "main",
  state: () => ({
    currentUser: {} as IUser,
    currentTenant: null as string | null,
    theme: null as string | null,
  }),
  actions: {
    hasPermission(permission: IUserPermission): boolean {
      return this.currentUser.tenants?.some(
        (t) =>
          t.tenantIdentifier === this.currentTenant &&
          t.permissions.some(
            (p) =>
              p.permissionZone === permission.permissionZone &&
              p.permissionType === permission.permissionType
          )
      ) || false;
    }
  }
});

然后在组件中调用:

onMounted(async () => {
  navItems.value = Navigation.filter((i) =>
    store.hasPermission(i.permission)
  );
});

内容的提问来源于stack exchange,提问作者Hades

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 09:45:49