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

export const useUserStore = defineStore('user', {
  // 修复1:删除重复的 state 定义,只保留根层级的 state
  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,
    dictData: cache.get(globalConfig.cache.dictDataKey) || {},
    logoutLoading: false
  }),

  getters: {
    isLogin: (state) => {
      if (!state.token) return false;
      if (state.expireTime && state.expireTime < Date.now()) {
        return false;
      }
      return true;
    },
    permissions: (state) => state.userInfo.permissions || []
  },

  actions: {
    async login(params) {
      try {
        const res = await login(params);
        const { accessToken, expiresTime, userId } = res;

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

        // 更新 Pinia state
        this.token = accessToken;
        this.expireTime = expiresTime;
        this.userId = userId;
        this.userInfo = {};

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

        // 获取用户信息
        const userInfo = await this.getUserInfo();
        this.userInfo = userInfo;

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

        // 修复2:调用字典接口时,不直接抛错(避免阻断登录)
        try {
          const dictRes = await this.getAndSaveDictData();
          // 仅接口返回成功时才更新字典
          this.dictData = dictRes || {};
          console.log('字典数据获取成功:', this.dictData);
        } catch (dictErr) {
          console.warn('获取字典失败(不影响登录):', dictErr);
          uni.showToast({ title: '获取字典失败,可正常使用', icon: 'none' });
        }

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

    // 修复3:重构字典获取方法(加 Token 校验 + 强制携带 Token + 宽松错误处理)
    async getAndSaveDictData() {
      // 前置校验:无登录态直接返回,不请求接口
      if (!this.isLogin) {
        console.warn('未登录,跳过字典获取');
        return { code: -1, msg: '未登录' };
      }

      try {
        // 强制携带 Token(和 getModuleList 保持一致,避免拦截器同步延迟)
        const res = await getSimpleDictDataList(
          {}, // 接口入参(按需传,比如 dictType: ['level'])
          { header: { 'Authorization': `Bearer ${this.token}` } }
        );

        // 校验接口返回码(核心:避免非 0 码数据存入)
        if (res.code !== 0) {
          console.warn('字典接口返回失败:', res.msg);
          return res; // 返回错误信息,但不抛错
        }
        return res;
      } catch (err) {
        // 修复4:宽松错误处理,只打印日志,不抛错(避免阻断登录)
        console.error('字典接口请求异常:', err);
        return { code: -2, msg: '接口请求异常:' + (err.message || '网络错误') };
      }
    },

    async getModuleList() {
      try {
        if (!this.token) {
          throw new Error('未获取到登录令牌,无法获取模块列表');
        }

        const res = await moduleList({}, {
          header: { 'Authorization': `Bearer ${this.token}` }
        });
        return res;
      } catch (err) {
        console.error('获取用户菜单失败:', err);
        if (err?.data?.code === 401 || err?.message.includes('401')) {
          throw new Error('登录态已过期,请重新登录');
        } else {
          throw new Error('获取用户菜单失败,请重新登录');
        }
      }
    },

    async getUserInfo() {
      try {
        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) {
        this.token = '';
        this.userInfo = {};
        this.userId = '';
        this.moduleListInfo = '';
        this.expireTime = 0;
        this.dictData = {};
        return;
      }

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

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

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

      logoutWithLock();
    },

    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;
    }
  },

  persist: {
    enabled: true,
    key: 'user_store',
    storage: {
      getItem: (key) => uni.getStorageSync(key),
      setItem: (key, value) => uni.setStorageSync(key, value),
      removeItem: (key) => uni.removeStorageSync(key)
    },
    serializer: {
      serialize: (state) => {
        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);
        uni.setStorageSync(globalConfig.cache.dictDataKey, state.dictData);
        return state;
      },
      deserialize: (value) => {
        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) || '',
          dictData: uni.getStorageSync(globalConfig.cache.dictDataKey) || {},
          logoutLoading: false
        };
      }
    },
    paths: []
  }
});