user.js 7.7 KB
import { defineStore } from 'pinia';
import cache from '@/common/utils/cache';
import globalConfig from '@/common/config/global';
import { login, getUserInfo, logout, moduleList } from '@/api/user';

export const useUserStore = defineStore('user', {
  // 1. 状态定义(修正 userId 类型 + 字段名)
  state: () => ({
    token: '',
    userInfo: {},
    userId: '',
    moduleListInfo: '',
    expireTime: 0,
    logoutLoading: false // 防重锁移到这里
  }),

  // 2. 计算属性(保持不变)
  getters: {
    isLogin: (state) => {
      if (!state.token) return false;
      if (state.expireTime && state.expireTime < Date.now()) {
        return false;
      }
      return true;
    },
    permissions: (state) => state.userInfo.permissions || []
  },

  // 3. 核心动作(修正语法错误 + 登录后自动调用户信息接口)
  actions: {
    async login(params) {
      try {
        const res = await login(params);
        const { accessToken, expiresTime, userId } = res;

        if (!accessToken) {
          throw new Error('登录失败,未获取到令牌');
        }

        // 仅更新 Pinia state(持久化插件会自动同步到 storage)
        this.token = accessToken;
        this.expireTime = expiresTime;
        this.userId = userId;
        this.userInfo = {};

        // 移除手动 cache.set() 代码
        // cache.set(globalConfig.cache.tokenKey, accessToken);
        // cache.set(globalConfig.cache.expireTimeKey, expiresTime);
        // cache.set(globalConfig.cache.userIdKey, userId);

        // 等待 Pinia 持久化同步(可选,50ms 足够)
        await new Promise(resolve => setTimeout(resolve, 50));

        // 获取用户信息
        const userInfo = await this.getUserInfo();
        this.userInfo = userInfo;
        // 移除 cache.set(globalConfig.cache.userInfoKey, userInfo);

        // 获取模块列表
        let moduleListInfo = null;
        try {
          moduleListInfo = await this.getModuleList();
          this.moduleListInfo = moduleListInfo;
          // 移除 cache.set(globalConfig.cache.moduleListKey, moduleListInfo);
        } catch (moduleErr) {
          console.warn('获取模块列表失败(不影响登录):', moduleErr);
          uni.showToast({ title: '获取模块列表失败,可正常登录', icon: 'none' });
        }

        return { ...res, userInfo, moduleListInfo };
      } catch (err) {
        console.error('登录流程失败:', err);
        throw err;
      }
    },

    async getModuleList() {
      try {
        // 前置校验:无 Token 直接抛错,避免无效请求
        if (!this.token) {
          throw new Error('未获取到登录令牌,无法获取模块列表');
        }

        // 强制携带 Token(覆盖请求工具的自动携带,避免缓存同步延迟)
        const res = await moduleList({}, {
          header: {
            'Authorization': `Bearer ${this.token}`
          }
        });
        return res;
      } catch (err) {
        console.error('获取用户菜单失败:', err);
        // 区分 401 错误:仅登录态失效时抛错,避免触发 logout 循环
        if (err?.data?.code === 401 || err?.message.includes('401')) {
          throw new Error('登录态已过期,请重新登录');
        } else {
          throw new Error('获取用户菜单失败,请重新登录');
        }
      }
    },

    async getUserInfo() {
      try {
        // 调用用户信息接口(此时 token 已存入缓存,请求工具会自动携带)
        const res = await getUserInfo();
        return res;
      } catch (err) {
        console.error('获取用户信息失败:', err);
        throw new Error('获取用户信息失败,请重新登录');
      }
    },
    logout() {
      const pages = getCurrentPages();
      if (pages.length === 0) return;

      const currentPageRoute = pages[pages.length - 1].route;
      const loginPath = globalConfig.router.loginPath
        .replace(/^\//, '')
        .split('?')[0];

      if (currentPageRoute === loginPath) {
        // 仅清空 Pinia state(持久化插件自动同步到 storage)
        this.token = '';
        this.userInfo = {};
        this.userId = '';
        this.moduleListInfo = '';
        this.expireTime = 0;

        // 移除手动 cache.remove() 代码
        // cache.remove(globalConfig.cache.tokenKey);
        // ... 其他 cache.remove 都删除
        return;
      }

      const logoutWithLock = async () => {
        if (this.logoutLoading) return;
        this.logoutLoading = true;

        try {
          await logout();
        } catch (err) {
          console.error('退出登录接口调用失败:', err);
        } finally {
          // 清空 state
          this.token = '';
          this.userInfo = {};
          this.userId = '';
          this.moduleListInfo = '';
          this.expireTime = 0;
          this.logoutLoading = false;

          // 移除手动 cache.remove() 代码

          uni.redirectTo({
            url: globalConfig.router.loginPath
          });
        }
      };

      logoutWithLock();
    },

// 新增:状态中添加 logoutLoading 防重锁
    state: () => ({
      token: cache.get(globalConfig.cache.tokenKey) || '',
      userInfo: cache.get(globalConfig.cache.userInfoKey) || {},
      userId: cache.get(globalConfig.cache.userIdKey) || '',
      moduleListInfo: cache.get(globalConfig.cache.moduleListKey) || '',
      expireTime: cache.get(globalConfig.cache.expireTimeKey) || 0,
      logoutLoading: false // 新增:退出登录防重锁
    }),

    checkLogin() {
      if (!this.isLogin) {
        // 先判断是否已在登录页,避免重复跳转
        const pages = getCurrentPages();
        if (pages.length === 0) return false;

        const currentPageRoute = pages[pages.length - 1].route;
        const loginPath = globalConfig.router.loginPath
          .replace(/^\//, '')
          .split('?')[0];

        if (currentPageRoute !== loginPath) {
          uni.redirectTo({
            url: globalConfig.router.loginPath
          });
        }
        return false;
      }
      return true;
    }
  },

  // 4. 持久化配置(修正:persist 应放在 store 根层级,非 actions 内)
  persist: {
    enabled: true,
    key: 'user_store', // 自定义存储键名(默认是 store 名 'user')
    storage: {
      getItem: (key) => uni.getStorageSync(key),
      setItem: (key, value) => uni.setStorageSync(key, value),
      removeItem: (key) => uni.removeStorageSync(key)
    },
    // 自定义序列化:将 state 拆分为原有的 jcss_xxx 键(可选)
    serializer: {
      serialize: (state) => {
        // 拆分为多个独立键(和原 cache 格式对齐)
        uni.setStorageSync(globalConfig.cache.tokenKey, state.token);
        uni.setStorageSync(globalConfig.cache.userIdKey, state.userId);
        uni.setStorageSync(globalConfig.cache.expireTimeKey, state.expireTime);
        uni.setStorageSync(globalConfig.cache.userInfoKey, state.userInfo);
        uni.setStorageSync(globalConfig.cache.moduleListKey, state.moduleListInfo);
        return state; // 返回完整 state(兼容 Pinia 默认逻辑)
      },
      deserialize: (value) => {
        // 从多个独立键恢复 state
        return {
          token: uni.getStorageSync(globalConfig.cache.tokenKey) || '',
          userId: uni.getStorageSync(globalConfig.cache.userIdKey) || '',
          expireTime: uni.getStorageSync(globalConfig.cache.expireTimeKey) || 0,
          userInfo: uni.getStorageSync(globalConfig.cache.userInfoKey) || {},
          moduleListInfo: uni.getStorageSync(globalConfig.cache.moduleListKey) || '',
          logoutLoading: false
        };
      }
    },
    paths: [] // 序列化自定义后,paths 可留空
  }
});