帮助文档
{{userInfo.nickname}}
用户设置 退出登录

{{wikiTitle}}

CRMEB前端状态管理说明

CRMEB前端状态管理说明

概述

CRMEB项目采用了Vue.js生态系统中的Vuex作为前端状态管理解决方案。项目包含两个前端应用:后台管理系统(admin)和移动端应用(uniapp),两者都使用了类似的Vuex模式,但根据各自的特点进行了定制化实现。

后台管理系统状态管理(admin)

架构设计

后台管理系统采用模块化的Vuex架构,主要特点如下:

  1. 根Store配置:位于 view/admin/src/store/index.js
    • 引入Vuex并注册到Vue实例
    • 导入admin模块作为命名空间
    • 使用模块化组织状态,便于维护
import Vue from \'vue\';
import Vuex from \'vuex\';

import admin from \'./modules/admin\'

Vue.use(Vuex);

export default new Vuex.Store({
    modules: {
        admin
    }
})
  1. 模块化结构view/admin/src/store/modules/admin/index.js
    • 动态导入所有子模块
    • 使用命名空间组织状态
    • 便于扩展和维护
/**
 * 该文件启用 `@/store/index.js` 导入所有 vuex 模块。
 * 这个文件是一次性创建的,不应该被修改。
 */

const files = require.context(\'./modules\', false, \\/\\.js\\$/);
const modules = {};

files.keys().forEach(key => {
    modules[key.replace(/(\\.\\/|\\.js)/g, \'\')] = files(key).default
});

export default {
    namespaced: true,
    modules
};

主要模块

1. 用户认证模块(account)

负责处理用户的登录、注册、登出等认证相关操作。

export default {
  namespaced: true,
  actions: {
    /**
     * @description 登录
     * @param {Object} param context
     * @param {Object} param username {String} 用户账号
     * @param {Object} param password {String} 密码
     * @param {Object} param route {Object} 登录成功后定向的路由对象 任何 vue-router 支持的格式
     */
    login({ dispatch }, { username = \"\", password = \"\" } = {}) {
      return new Promise((resolve, reject) => {
        // 开始请求登录接口
        AccountLogin({
          username,
          password,
        })
          .then(async (res) => {
            // 设置 cookie 一定要存 uuid 和 token 两个 cookie
            // 整个系统依赖这两个数据进行校验和存储
            // uuid 是用户身份唯一标识 用户注册的时候确定 并且不可改变 不可重复
            // token 代表用户当前登录状态 建议在网络请求中携带 token
            // 如有必要 token 需要定时更新,默认保存一天,可在 setting.js 中修改
            // 如果你的 token 不是通过 cookie 携带,而是普通字段,也可视情况存储在 localStorage
            util.cookies.set(\"uuid\", res.uuid);
            util.cookies.set(\"token\", res.token);
            // 设置 vuex 用户信息
            await dispatch(\"admin/user/set\", res.info, { root: true });
            // 用户登录后从持久化数据加载一系列的设置
            await dispatch(\"load\");
            // 结束
            resolve();
          })
          .catch((err) => {
            reject(err);
          });
      });
    },
    /**
     * @description 退出登录
     * */
    logout({ commit, dispatch }, { confirm = false, vm } = {}) {
      async function logout() {
        AccountLogout()
          .then(() => {
            // localStorage.clear();
            // sessionStorage.clear()
            let isAgentAdmin = Vue.prototype.__isAgentPath();
            if (isAgentAdmin) {
              util.cookies.remove(\"agent_token\");
              util.cookies.remove(\"agent_uuid\");
              // 删除localStorage
              store.dispatch(\"admin/db/databaseClear\", {
                user: true,
                isAgent: true
              });
            } else {
              util.cookies.remove(\"token\");
              util.cookies.remove(\"expires_time\");
              util.cookies.remove(\"uuid\");
              // 删除localStorage
              store.dispatch(\"admin/db/databaseClear\", {
                user: true,
              });
              // 清空 vuex 用户信息
              dispatch(\"admin/user/set\", {}, { root: true });
            }
            // 跳转路由
            router.push({
              name: isAgentAdmin ? \"agentLogin\" : \"login\",
            });
          })
          .catch(() => {});
      }
      if (confirm) {
        Modal.confirm({
          title: vm.$t(\"basicLayout.logout.confirmTitle\"),
          content: vm.$t(\"basicLayout.logout.confirmContent\"),
          onOk() {
            logout();
          },
        });
      } else {
        logout();
      }
    }
  }
};

2. 用户信息模块(user)

管理用户的基本信息、权限等数据。

3. 布局配置模块(layout)

管理系统的界面布局配置,如菜单折叠状态、全屏模式等。

export default {
    namespaced: true,
    state: {
        ...Setting.layout,
        isMobile: false, // 是否为手机
        isTablet: false, // 是否为平板
        isDesktop: true, // 是否为桌面
        isFullscreen: false, // 是否切换到了全屏
        copyrightShow: true,  // 是否显示底部版权
        menuCollapse: JSON.parse(window.localStorage.getItem(\'menuCollapse\')) || false  //侧边菜单栏是否默认折起
    },
    mutations: {
        /**
         * @description 设置设备类型
         * @param {Object} state vuex state
         * @param {String} type 设备类型,可选值为 Mobile、Tablet、Desktop
         */
        setDevice (state, type) {
            state.isMobile = false;
            state.isTablet = false;
            state.isDesktop = false;
            state[`is${type}`] = true;
        },
        /**
         * @description 修改 menuCollapse
         * @param {Object} state vuex state
         * @param {Boolean} collapse 折叠状态
         * */
        updateMenuCollapse (state, collapse) {
            let storage = window.localStorage;
            storage.setItem(\'menuCollapse\', collapse);
            state.menuCollapse = collapse;
        },
        /**
         * @description 设置全屏状态
         * @param {Object} state vuex state
         * @param {Boolean} isFullscreen vuex
         * */
        setFullscreen (state, isFullscreen) {
            state.isFullscreen = isFullscreen;
        }
    },
    actions: {
        /**
         * @description 切换全屏
         */
        toggleFullscreen ({ commit }) {
            return new Promise(resolve => {
                if (screenfull.isFullscreen) {
                    screenfull.exit();
                    commit(\'setFullscreen\', false);
                } else {
                    screenfull.request();
                    commit(\'setFullscreen\', true);
                }
                resolve();
            });
        }
    }
};

4. 数据库模块(db)

提供本地数据持久化功能。

状态管理最佳实践

  1. 命名空间:所有模块都使用命名空间避免冲突
  2. 状态持久化:通过localStorage保存用户偏好设置
  3. 异步操作:使用actions处理异步请求
  4. 状态同步:通过mutations确保状态变化可追踪

移动端状态管理(uniapp)

架构设计

移动端应用同样采用Vuex进行状态管理,结构更加轻量化:

  1. 根Store配置:位于 view/uniapp/store/index.js
    • 导入模块和getter
    • 在非生产环境中启用严格模式
import Vue from \"vue\";
import Vuex from \"vuex\";
import modules from \"./modules\";
import getters from \"./getters\";

Vue.use(Vuex);
const debug = process.env.NODE_ENV !== \"production\";

export default new Vuex.Store({
  modules,
  getters,
  strict: debug
});
  1. 模块组织view/uniapp/store/modules/index.js
    • 集中导出所有模块
import app from \"./app\";
import hotWords from \"./hotWords\";
import indexData from \'./indexData.js\'
export default {
  app,
  hotWords,
  indexData
};

主要模块

1. 应用主模块(app)

管理用户认证、用户信息、个性化配置等核心状态:

import { getUserInfo } from \"@/api/user.js\";
import { diyProductApi } from \"@/api/store.js\"
import { getAgentBrokerage } from \"@/api/public.js\"
import {
    LOGIN_STATUS,
    NON_WIFI_AUTOPLAY,
    UID,
    USER_INFO,
    STORE_NUM,
    STORE_STAFF_INFO,
} from \'@/config/cache\';
import Cache from \'@/utils/cache\';

const state = {
    token: Cache.get(LOGIN_STATUS) || false,           // 用户认证令牌
    backgroundColor: \"#fff\",                          // 背景色
    userInfo: Cache.get(USER_INFO) || {},             // 用户信息
    uid: Cache.get(UID) || 0,                       // 用户ID
    homeActive: false,                               // 首页激活状态
    phoneStatus: true,                               // 电话状态
    pageFooter: uni.getStorageSync(\'pageFoot\') || {}, // 页面底部
    autoplay: Cache.get(NON_WIFI_AUTOPLAY) || false,  // 自动播放设置
    // 商品详情可视化数据
    diyProduct: {
        navList: [0, 1, 2, 3, 4], // 顶部菜单内容
        openShare: 1, //是否开启分享
        pictureConfig: 0, //轮播图模式 0 固定方图 1 高度自适应
        swiperDot: 1, //是否展示轮播指示点
        showPrice: [0, 1], //是否显示付费会员价和等级会员
        isOpen: [0, 1, 2], //是否展示 0 划线价 1 累计销量 2 库存
        showSvip: 1, //是否展示付费会员卡片
        showRank: 1, // 是否展示排行榜卡片
        showService: [0, 1, 2, 3], //服务区卡片 0 营销活动入口 1 sku选择 2 服务保障 3 参数
        showReply: 1, //是否展示评论区
        replyNum: 3, //评论数量
        showMatch: 1, //是否展示搭配购
        matchNum: 3, //搭配套餐数量
        showRecommend: 1, //是否展示推荐商品
        recommendNum: 12, //推荐商品数量
        menuList: [0, 1, 2], //底部左侧菜单
        showCart: 1, //是否显示购物车
        showCommunity: 0, //是否显示种草
    },
    // 商品分类可视化数据
    diyCategory: {
        level: 2,
        index: 0
    },
    productVideoStatus: true,                        // 产品视频状态
    nearbyStore: 0,                                 // 附近门店
    storeNum: Cache.get(STORE_NUM) || 0,            // 门店类型
    storeStaffInfo: Cache.get(STORE_STAFF_INFO, true) || {}, // 店员门店信息
    brokerageFuncStatus: 0,                         // 分销启用状态
    storeBrokerageStatu: 1,                         // 分销模式
    storeBrokeragePrice: 0,                         // 满额分销最低消费金额
};

const mutations = {
    SETPHONESTATUS(state, val) {
        state.phoneStatus = val;
    },
    LOGIN(state, opt) {
        state.token = opt.token;
        Cache.set(LOGIN_STATUS, opt.token, opt.time);
    },
    SETUID(state, val) {
        state.uid = val;
        Cache.set(UID, val);
    },
    UPDATE_LOGIN(state, token) {
        state.token = token;
    },
    LOGOUT(state) {
        Cache.clear(LOGIN_STATUS);
        Cache.clear(UID);
        Cache.clear(USER_INFO);
        Cache.clear(\'newcomerGift\');
        state.token = undefined;
        state.uid = undefined
        state.userInfo = {}
    },
    BACKGROUND_COLOR(state, color) {
        state.color = color;
        document.body.style.backgroundColor = color;
    },
    UPDATE_USERINFO(state, userInfo) {
        state.userInfo = userInfo;
        Cache.set(USER_INFO, userInfo);
    },
    OPEN_HOME(state) {
        state.homeActive = true;
    },
    CLOSE_HOME(state) {
        state.homeActive = false;
    },
    FOOT_UPLOAD(state, data) {
        state.pageFooter = data
    },
    SET_AUTOPLAY(state, data) {
        state.autoplay = data
        Cache.set(NON_WIFI_AUTOPLAY, data)
    },
    SET_PRODUCT_DIY(state, data) {
        state.diyProduct = data.product_detail;
        state.diyCategory = data.product_category;
        state.productVideoStatus = data.product_video_status;
    }
};

const actions = {
    USERINFO({
        state,
        commit
    }, force) {
        if (state.userInfo !== null && !force)
            return Promise.resolve(state.userInfo);
        else
            return new Promise(resolve => {
                getUserInfo().then(res => {
                    commit(\"UPDATE_USERINFO\", res.data);
                    Cache.set(USER_INFO, res.data);
                    resolve(res.data);
                });
            }).catch(() => {

            });
    },
    // div商品详情
    async getDiyProduct({commit}) {
        let result = await diyProductApi();
        if(result.status == 200){
            commit(\"SET_PRODUCT_DIY\",result.data);
        }
    },
    // 获取分销信息
    async getBrokerage({ commit }) {
        let result = await getAgentBrokerage();
        if (result.status == 200) {
            commit(\"SET_BROKERAGE_STATUS\", result.data);
        }
    }
};

export default {
    state,
    mutations,
    actions
};

2. 首页数据模块(indexData)

管理首页相关数据,如购物车数量等:

import {CART_NUM} from \'@/config/cache\';
import Cache from \'@/utils/cache\';
export default {
    namespaced: true,
    state: {
        cartNum: Cache.get(CART_NUM) || 0
    },
    getters: {},
    mutations: {
        setCartNum(state, data) {
            Cache.set(CART_NUM, data);
            state.cartNum = data;
        }
    }
}

3. 热词模块(hotWords)

管理搜索相关的热词数据:

export default {
    namespaced: true,
    state: {
        // 搜索关键字
        hotWord: []
    },
    getters: {},
    mutations: {
        setHotWord(state, fastsearchforhotwords) {
            state.hotWord = fastsearchforhotwords;
        }
    }
}

全局Getters

移动端提供了便捷的全局状态访问接口:

export default {
    token: state => state.app.token,
    isLogin: state => !!state.app.token,
    backgroundColor: state => state.app.backgroundColor,
    userInfo: state => state.app.userInfo || {},
    uid: state => state.app.uid,
    homeActive: state => state.app.homeActive,
    home: state => state.app.home,
    cartNum: state => state.indexData.cartNum,
    diyProduct: state => state.app.diyProduct,
    diyCategory: state => state.app.diyCategory,
    productVideoStatus: state => state.app.productVideoStatus,
    storeNum: state => state.app.storeNum
};

状态管理最佳实践

1. 状态持久化策略

  • 后台管理:使用localStorage保存用户偏好设置(如菜单折叠状态)
  • 移动端:使用自定义Cache工具类保存用户认证信息和配置

2. 用户认证状态管理

  • Token管理:通过Cookie或LocalStorage保存认证令牌
  • 用户信息:保持用户信息在应用生命周期内的同步
  • 登出清理:确保登出时清除所有相关状态和缓存

3. 模块化设计原则

  • 单一职责:每个模块只负责特定领域的状态
  • 命名空间:避免不同模块间的状态冲突
  • 状态隔离:模块间保持相对独立,通过明确接口交互

4. 异步操作处理

  • API调用:在actions中处理异步请求
  • 错误处理:统一的错误处理机制
  • 状态同步:确保异步操作后的状态一致性

状态映射辅助函数

在组件中使用Vuex提供的辅助函数可以简化状态访问:

后台管理示例

import { mapState, mapGetters, mapMutations, mapActions } from \'vuex\';

export default {
  computed: {
    ...mapState(\'admin\', [\'token\', \'userInfo\']),
    ...mapGetters([\'isLogin\'])
  },
  methods: {
    ...mapMutations([\'UPDATE_USERINFO\']),
    ...mapActions(\'admin/account\', [\'login\', \'logout\'])
  }
}

移动端示例

import { mapState, mapGetters, mapMutations, mapActions } from \'vuex\';

export default {
  computed: {
    ...mapState(\'app\', [\'token\', \'userInfo\']),
    ...mapGetters([\'isLogin\', \'cartNum\'])
  },
  methods: {
    ...mapMutations(\'app\', [\'LOGIN\', \'LOGOUT\']),
    ...mapActions(\'app\', [\'USERINFO\'])
  }
}

注意事项

  1. 性能考虑:对于频繁变化的状态,注意避免不必要的重新渲染
  2. 内存管理:及时清理不需要的状态,防止内存泄漏
  3. 调试支持:开发环境下启用严格模式,便于调试状态变化
  4. 版本兼容:确保Vuex版本与Vue版本兼容
  5. 安全性:敏感信息(如token)的安全存储和传输

常见问题

1. 状态更新不生效

  • 检查是否通过mutation修改状态
  • 确认state对象是响应式的

2. 组件无法获取最新状态

  • 检查mapState或computed计算属性的映射是否正确
  • 确认状态更新是否正确触发了组件重渲染

3. 状态持久化失败

  • 检查浏览器是否禁用了localStorage
  • 确认缓存大小限制是否超出

4. 多模块状态同步问题

  • 使用namespace避免命名冲突
  • 通过dispatch调用其他模块的action进行跨模块通信
{{cateWiki.like_num}}人点赞
0人点赞
评论({{cateWiki.comment_num}}) {{commentWhere.order ? '评论从旧到新':'评论从新到旧'}} {{cateWiki.page_view_num}}人看过该文档
评论(0) {{commentWhere.order ? '评论从旧到新':'评论从新到旧'}} 16人看过该文档
评论
{{item.user ? item.user.nickname : ''}} (自评)
{{item.content}}
{{item.create_time}} 删除
{{item.like ? item.like.like_num : 0}} {{replyIndex == index ? '取消回复' : '回复'}}
评论
{{items.user ? items.user.nickname : '暂无昵称'}} (自评)
{{items.content}}
{{items.create_time}} 删除
{{items.like ? items.like.like_num : 0}} {{replyIndexJ == (index+'|'+indexJ) ? '取消回复' : '回复'}}
评论
目录
  • {{item}}