状态管理应用

@ Pinia
一切皆仓库
远程应用
商品仓库 goods.js
1. 所有商品 goods
const goods = ref([])
2. 单个商品 good
const good = ref({})
3. 获取所有商品 getGoods()
. 获取商品数据;方法、来源可自定;这里以大树小站数据为例
const getGoods = async () => {
  let res = await fetch("https://glpla.github.io/utils/data/coffee.json")
  let json = await res.json()
  // 更新状态
  goods.value = json.cont;
  // 显式返回结果,便于组件使用
  return json.cont;
};
4. 根据 id 获取所有商品 getGoodById(id)
const getGoodById = async (id) => {
  // 如果第一次使用仓库,则先获取商品数据
  if (goods.value.length === 0) {
    await getGoods();
  }
  // 注意 === 的限制;如果定义的id是普通数值0,则可能匹配失败,因为所有的参数都是字符串类型;'0' === 0,为假
  let res = goods.value.find((good) => good.id == id);
  // 更新状态
  good.value = res;
  // 显式返回结果,便于组件使用
  return res;
};
5. 根据关键字获取 - 查找 title
const getByQuery = async (query) => {
  if (!goods.value.length) {
    await getAll();
  }
  let res = goods.value.filter((item) => item.title.includes(query));
  return res;
};
6. 分页获取*
7. 加载指示*
购物车仓库 cart.js
1. 购物车 carts
const lists = ref([])
2. 添加到购物车 addToCarts(product)
. 如果商品不存在,则为当前商品添加新的 id 作为对应的购物车数据项的 id,该 id 会覆盖商品的 id;如果商品数据项已经存在,则数量相应的增加
. 其它业务:库存调整,需要使用商品仓库 goods.js - 也可以放在订单中处理,库存才真正变化
. 这里使用第三方库 NPM - Nanoid 生成随机 id
const addToCarts = (product) => {
  const existingItem = lists.value.find((item) => item.productId === product.productId);
  if (existingItem) {
    existingItem.quantity += product.quantity;
  } else {
    // replace id with nanoid
    lists.value.push({ 
      ...product, 
      id: nanoid(), 
      create_time: Date.now()
    });
  }
};
3. 从购物车中移除 removeFromCart(id)
const removeFromCart = (id) =>{
  lists.value = lists.value.filter(item => item.id !== id)
}
4. 清空购物车 clearCarts()
const clearCarts = () => {
  lists.value = [];
};
5. 从购物车移除* deleteFromToCarts(product)
订单仓库 order.js
. 单击结算时,创建临时订单;订单支付成功,创建订单
1. 订单 orders
2. 临时订单 tempOrders
3. 订单总价 sum
const sum = computed(() => {
  return orders.value.reduce(
    (total, item) => total + item.quantity * item.price * (1 - item.discount),
    0
  );
});
4. 订单折扣 discount
const discount = computed(() => {
  return orders.value.reduce(
    (total, item) => total + item.quantity * item.price * item.discount,
    0
  );
});
其它仓库
[] 点赞仓库
. 创建任意仓库,并创建一个状态 num
. 组件A中引入并使用;采用简单模板语法 {{num}}
. 组件B中引入并使用;采用双向绑定 v-mode
. 入口组件中引入并使用组件A、组件B,查看双向绑定时状态的共享变化
[]用户状态
[]访问量统计仓库
[]位置仓库
本地应用
[]主题切换仓库

. 主题切换/换肤/日夜模式

. 通过为 html 指定自定义属性实现 CSS 变量的更新达到切换效果

主题样式 - 这里仅给出需要变化的 CSS 变量,其它变量可以在额外的 :root 单独定义
:root {
    --txt-color: #303133;
    --bg-color: #f1f1f1;
    // 其它变量...
}

// 类形式
.dark-theme {
    --txt-color: #f1f1f1;
    --bg-color: #131313;
}

// 自定义属性形式
[data-theme="dark"] {
    --txt-color: #f1f1f1;
    --bg-color: #131313;
}
主题仓库:两种方案都可以;保留一种即可
import { defineStore } from 'pinia'
import { ref, onMounted } from 'vue'
export const useThemeStore = defineStore('theme', () => {

  const isDarkTheme = ref(false);

  const toggleDarkTheme = () => {
    isDarkTheme.value = !isDarkTheme.value;
    //  方案1:设置针自定义属性
    document.documentElement.setAttribute('data-theme', isDarkTheme.value ? 'dark' : 'light');
    // 方案2:设置类
    // document.documentElement.classList.toggle("dark-theme");
  };

  return { isDarkTheme, toggleDarkTheme }
})
在SFC中引入仓库,并使用相应的图标展示暗黑和白天,最后绑定主题切换事件
import { useThemeStore } from '@/stores/theme';
const themeStore = useThemeStore();
<div class="theme-icon" @click="themeStore.toggleDarkTheme">
    <span class="iconfont" :class="themeStore.isDarkTheme ? 'icon-Moon-Star' : 'icon-Sun'"></span>
</div>
[] 汉堡菜单仓库
项目从桌面端切换到移动端时,菜单折叠,由汉堡按钮控制折叠和展开;单击菜单项时,折叠菜单
1. 创建仓库 - 仓库参考代码 menu.js
import { ref } from 'vue'
import { defineStore } from 'pinia'

export const useMenuStore = defineStore('menu', () => {
  const flag = ref(false)
  const switchFlag = () => {
    flag.value ? flag.value = false : flag.value = true
  }

  return { flag, switchFlag }
})
2. 在使用仓库的组件结构中,引入仓库,通过汉堡按钮的单击,由标记 flag 控制动态类的应用,达到菜单的折叠和展开
改进:单击菜单项,折叠菜单
提示:在路由配置中,引入仓库,在导航前置守卫中实例化仓库并切换标记 switchFlag
在路由文件中,引入仓库 menu.js
每次进入,创建菜单实例并设置菜单栏样式为隐藏
import { useMenuStore } from '@/stores/menu'

router.beforeEach((to, from, next) => {
    // 其它逻辑

    // MUST
    const store = useMenuStore();
    store.flag = false;
    next();
})

router.afterEach((to, from) => {
    // 其它逻辑

    if (to.path === from.path) {
        const store = useMenuStore()
        store.flag = false
    }
})
守卫中的两个 store 返回的是同一个 Pinia store 实例。虽然变量名相同,但是在不同的作用域中,引用的都是同一个对象实例,因此不会导致逻辑上的冲突
更多路由控制,应由后端服务器实现,前端不可靠
路由配置时,如果不是每个路由都指定 meta,使用时应先判断再使用
浏览器的返回和前进,不会触发守卫