安装 vue项目

npm init vue@latest  回车

装包

npm install

路由

安装 Router

npm install vue-router@4 -S

项目根目录新建 router --> index.js

vue2中写法

index.js

import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from '../views/Home.vue';

Vue.use(VueRouter);

const router = new VueRouter({
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home
    }
  ]
});

export default router;


main.js

import Vue from 'vue';
import App from './App.vue';
import router from './router';

new Vue({
  router
}).$mount('#app');

vue3中写法

index.js

import { createRouter, createWebHistory } from "vue-router";
import Home from "../views/Home.vue";

const routes = [
  {
    path: "/",
    name: "Home",
    component: Home,
  },
  {
    path: "/about",
    name: "About",
    component: () =>
      import(/* webpackChunkName: "about" */ "../views/About.vue"),
  },
];

const router = createRouter({
  history: createWebHistory(),
  routes,
});

export default router;




main.js

import { createApp } from 'vue';
import App from './App.vue';
import { router } from './router';

const app = createApp(App);
app.use(router);
app.mount('#app');

路由缓存问题

问题展示

一级分类的切换正好满足上边的条件,组件示例服用,导致分类数据无法更新

解决思路:

1. 让组件实例不复用,强制销毁重建 

<RouterView :key="$route.fullPath" />

2. 监听路由变化,变化之后执行数据更新操作 (可以精细化控制)

路由参数变化的时候 重新请求接口

import {  onBeforeRouteUpdate } from 'vue-router'

onBeforeRouteUpdate(async (to, from) => {
      console.log(to)
})

es6新特性:默认参数

useRoute 和 useRouter

useRoute 主要用于获取当前路由的信息,而 useRouter 主要用于获取整个应用中的路由器实例,方便进行全局的导航。

列表无限加载功能

实现逻辑:

监听是否满足触底条件,满足时让页数加1获取下一页数据,做新老数据拼接渲染,加载完毕结束监听

可借助    监听是否触底

指定路由切换时的滚动位置

借助 scrollBehavior

滚动到顶部

scrollBehavior(to, from, savedPosition) {
    // always scroll to top
    return { top: 0 }
  },

ES6中js的运算符 ?.?:???=

const user = {
  name: 'John',
  address: {
    city: 'New York',
  },
};

// 在可选链上使用 ?. 防止空指针异常
const city = user?.address?.city; // 如果 address 或 city 为 null/undefined,不会抛出异常
console.log(city); // 输出: New York

const age = 20;
const status = age >= 18 ? 'Adult' : 'Minor';
console.log(status); // 如果 age 大于等于 18,输出: Adult;否则输出: Minor

const defaultValue = 'Default';
const userValue = null;

const result = userValue ?? defaultValue;
console.log(result); // 如果 userValue 为 null 或 undefined,输出: Default;否则输出 userValue 的值

let existingValue = 'Existing Value';
let newValue = null;

// 只有当 newValue 不为 null 或 undefined 时才进行赋值
existingValue ??= newValue;
console.log(existingValue); // 如果 newValue 为 null 或 undefined,existingValue 的值不变;否则将 existingValue 赋值为 newValue

通过小图切换大图显示

 

 1.准备图片列表

2.左边是大图 右边是小图列表

3.给小图加上鼠标移入事件 当鼠标移入时,获取当前图片的下标值并给左边大图显示

4.当下标值和目标值相等时,加入激活类

<script setup>
import { ref } from "vue";
// 图片列表
const imageList = [
  "https://yanxuan-item.nosdn.127.net/d917c92e663c5ed0bb577c7ded73e4ec.png",
  "https://yanxuan-item.nosdn.127.net/e801b9572f0b0c02a52952b01adab967.jpg",
  "https://yanxuan-item.nosdn.127.net/b52c447ad472d51adbdde1a83f550ac2.jpg",
  "https://yanxuan-item.nosdn.127.net/f93243224dc37674dfca5874fe089c60.jpg",
  "https://yanxuan-item.nosdn.127.net/f881cfe7de9a576aaeea6ee0d1d24823.jpg",
];

// 小图切换大图
const activeIndex = ref(0);

// 鼠标移入事件
const enterhandler = (i) => {
  activeIndex.value = i;
};
</script>


<template>
  <div class="goods-image">
    <!-- 左侧大图-->
    <div class="middle" ref="target">
      <img :src="imageList[activeIndex]" alt="" style="width: 400px" />
      <!-- 蒙层小滑块 -->
      <div class="layer" :style="{ left: `0px`, top: `0px` }"></div>
    </div>
    <!-- 小图列表 -->
    <ul class="small">
      <li
        v-for="(img, i) in imageList"
        :key="i"
        @mouseenter="enterhandler(i)"
        :class="{ active: i === activeIndex }"
      >
        <img :src="img" alt="" style="height: 70px" />
      </li>
    </ul>
    <!-- 放大镜大图 -->
    <div
      class="large"
      :style="[
        {
          backgroundImage: `url(${imageList[0]})`,
          backgroundPositionX: `0px`,
          backgroundPositionY: `0px`,
        },
      ]"
      v-show="false"
    ></div>
  </div>
</template>

<style scoped lang="scss">
.goods-image {
  width: 480px;
  height: 400px;
  position: relative;
  display: flex;

  .middle {
    width: 400px;
    height: 400px;
    background: #f5f5f5;
  }

  .large {
    position: absolute;
    top: 0;
    left: 412px;
    width: 400px;
    height: 400px;
    z-index: 500;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    background-repeat: no-repeat;
    // 背景图:盒子的大小 = 2:1  将来控制背景图的移动来实现放大的效果查看 background-position
    background-size: 800px 800px;
    background-color: #f8f8f8;
  }

  .layer {
    width: 200px;
    height: 200px;
    background: rgba(0, 0, 0, 0.2);
    // 绝对定位 然后跟随咱们鼠标控制left和top属性就可以让滑块移动起来
    left: 0;
    top: 0;
    position: absolute;
  }

  .small {
    width: 80px;

    li {
      width: 68px;
      height: 68px;
      margin-left: -20px;
      margin-bottom: 15px;
      cursor: pointer;

      &:hover,
      &.active {
        border: 2px solid #009bf5;
      }
    }
  }
}
ul li {
  list-style: none;
}
</style>

全局组件统一插件化

全局注册组件,页面使用时 无需再导入,直接使用即可

1. 新建 index.js

// 把components中的所组件都进行全局化注册
// 通过插件的方式

import Sku from "./XtxSku/index.vue";
export const componentPlugin = {
  install(app) {
    // app.component('组件名字',组件配置对象)
    app.component("XtxSku", Sku);
  },
};

2. main.js 注册


// 引入全局组件插件
import { componentPlugin } from '@/components'

app.use(componentPlugin)

 3. 页面使用

放大镜效果实现

1. 获取鼠标在大图中的坐标

2. 放入滑块,并监听鼠标的位置,得出滑块的位置,得出放大镜大图的位置

3. 是否显示隐藏滑块 和 放大镜大图

<script setup>
import { ref, watch } from "vue";
import { useMouseInElement } from "@vueuse/core";
// 图片列表
const imageList = [
  "https://yanxuan-item.nosdn.127.net/d917c92e663c5ed0bb577c7ded73e4ec.png",
  "https://yanxuan-item.nosdn.127.net/e801b9572f0b0c02a52952b01adab967.jpg",
  "https://yanxuan-item.nosdn.127.net/b52c447ad472d51adbdde1a83f550ac2.jpg",
  "https://yanxuan-item.nosdn.127.net/f93243224dc37674dfca5874fe089c60.jpg",
  "https://yanxuan-item.nosdn.127.net/f881cfe7de9a576aaeea6ee0d1d24823.jpg",
];

// 小图切换大图
const activeIndex = ref(0);

// 鼠标移入事件
const enterhandler = (i) => {
  activeIndex.value = i;
};

// 获取鼠标相对位置
const target = ref(null);
const { elementX, elementY, isOutside } = useMouseInElement(target);

// 控制滑块跟随鼠标移动(监听elementX/Y变化,一旦变化 重新设置left/top)
const left = ref(0);
const top = ref(0);

// 大图坐标
const positionX = ref(0);
const positionY = ref(0);

watch([elementX, elementY, isOutside], () => {
  if (isOutside.value) return;
  // 有效范围内控制滑块距离
  // 横向
  if (elementX.value > 100 && elementX.value < 300) {
    left.value = elementX.value - 100;
  }
  // 纵向
  if (elementY.value > 100 && elementY.value < 300) {
    top.value = elementY.value - 100;
  }

  // 处理边界
  if (elementX.value > 300) {
    left.value = 200;
  }
  if (elementX.value < 100) {
    left.value = 0;
  }

  if (elementY.value > 300) {
    top.value = 200;
  }
  if (elementY.value < 100) {
    top.value = 0;
  }

  // 控制大图的显示
  positionX.value = -left.value * 2;
  positionY.value = -top.value * 2;
});
</script>


<template>
  <div class="goods-image">
    <!-- 左侧大图-->
    <div class="middle" ref="target">
      <img :src="imageList[activeIndex]" alt="" style="width: 400px" />
      <!-- 蒙层小滑块 -->
      <div
        class="layer"
        v-show="!isOutside"
        :style="{ left: `${left}px`, top: `${top}px` }"
      ></div>
    </div>
    <!-- 小图列表 -->
    <ul class="small">
      <li
        v-for="(img, i) in imageList"
        :key="i"
        @mouseenter="enterhandler(i)"
        :class="{ active: i === activeIndex }"
      >
        <img :src="img" alt="" style="height: 70px" />
      </li>
    </ul>
    <!-- 放大镜大图 -->
    <div
      class="large"
      :style="[
        {
          backgroundImage: `url(${imageList[activeIndex]})`,
          backgroundPositionX: `${positionX}px`,
          backgroundPositionY: `${positionY}0px`,
        },
      ]"
      v-show="!isOutside"
    ></div>
  </div>
</template>

<style scoped lang="scss">
.goods-image {
  width: 480px;
  height: 400px;
  position: relative;
  display: flex;

  .middle {
    width: 400px;
    height: 400px;
    background: #f5f5f5;
  }

  .large {
    position: absolute;
    top: 0;
    left: 412px;
    width: 400px;
    height: 400px;
    z-index: 500;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    background-repeat: no-repeat;
    // 背景图:盒子的大小 = 2:1  将来控制背景图的移动来实现放大的效果查看 background-position
    background-size: 800px 800px;
    background-color: #f8f8f8;
  }

  .layer {
    width: 200px;
    height: 200px;
    background: rgba(0, 0, 0, 0.2);
    // 绝对定位 然后跟随咱们鼠标控制left和top属性就可以让滑块移动起来
    left: 0;
    top: 0;
    position: absolute;
  }

  .small {
    width: 80px;

    li {
      width: 68px;
      height: 68px;
      margin-left: -20px;
      margin-bottom: 15px;
      cursor: pointer;

      &:hover,
      &.active {
        border: 2px solid #009bf5;
      }
    }
  }
}
ul li {
  list-style: none;
}
</style>

SKU组件

<script setup>
import { onMounted, ref } from "vue";
import axios from "axios";
import powerSet from "./power-set";
// 商品数据
const goods = ref({});

// 数据获取完毕生成路径字典
const getGoods = async () => {
  // 1135076  初始化就有无库存的规格
  // 1369155859933827074 更新之后有无库存项(蓝色-20cm-中国)
  const res = await axios.get(
    "http://pcapi-xiaotuxian-front-devtest.itheima.net/goods?id=1135076"
  );
  goods.value = res.data.result;
  const pathMap = getPathMap(goods.value);
  console.log("pathMap==", pathMap);
  // 初始化更新按钮状态
  initDisabledState(goods.value.specs, pathMap);
};
onMounted(() => getGoods());

// 切换选中状态
const changeSku = (item, val) => {
  if (val.disabled) return;
  // item 同一排的对象
  //  val 当前点击项
  if (val.selected) {
    // 如果当前是激活状态 则取消激活
    val.selected = false;
  } else {
    // 如果当前是未激活状态 则取消同排的激活状态 并激活自己
    item.values.forEach((val) => (val.selected = false));
    val.selected = true;
  }
};

// 创建生成路径字典对象函数
const getPathMap = (goods) => {
  // console.log("goods.skus===", goods.skus);
  const pathMap = {};
  // 得到所有有效的sku集合
  const effectiveSkus = goods.skus.filter((sku) => sku.inventory > 0);
  // console.log("effectiveSkus===========", effectiveSkus);
  // 根据有效的sku集合使用算法得到所有的子集 [1,2] => [[1], [2], [1,2]]
  effectiveSkus.forEach((sku) => {
    // 获取可选规格值数组
    const selectedValArr = sku.specs.map((val) => val.valueName);
    // console.log("selectedValArr=====", selectedValArr); // 多个数组 格式 ['黑色', '20cm', '中国']
    //  获取可选值数组的子集
    const valueArrPowerSet = powerSet(selectedValArr);
    // console.log("valueArrPowerSet=====", valueArrPowerSet);
    /* [
  [],
  ['黑色'],
  ['20cm'],
  ['黑色', '20cm'],
  ['中国'],
  ['黑色', '中国'],
  ['20cm', '中国'],
  ['黑色', '20cm', '中国']
]
 */
    //  根据子集生成路径字典对象
    valueArrPowerSet.forEach((arr) => {
      // 根据Arr得到字符串的key,约定使用-分割 ['蓝色','美国'] => '蓝色-美国'
      const key = arr.join("-");
      // 给pathMap设置数据
      if (pathMap[key]) {
        pathMap[key].push(sku.id);
      } else {
        pathMap[key] = [sku.id];
      }
    });
    // console.log("pathMap===", pathMap);
  });
  return pathMap;
};

// 1. 定义初始化函数
// specs:商品源数据 pathMap:路径字典
const initDisabledState = (specs, pathMap) => {
  // 约定:每一个按钮的状态由自身的disabled进行控制
  specs.forEach((item) => {
    item.values.forEach((val) => {
      // 路径字典中查找是否有数据 有-可以点击 没有-禁用
      val.disabled = !pathMap[val.name];
    });
  });
};
</script>

<template>
  <div class="goods-sku">
    <dl v-for="item in goods.specs" :key="item.id">
      <dt>{{ item.name }}</dt>
      <dd>
        <template v-for="val in item.values" :key="val.name">
          <!-- 图片类型规格 -->
          <img
            v-if="val.picture"
            @click="changeSku(item, val)"
            :class="{ selected: val.selected, disabled: val.disabled }"
            :src="val.picture"
            :title="val.name"
          />
          <!-- 文字类型规格 -->
          <span
            v-else
            :class="{ selected: val.selected, disabled: val.disabled }"
            @click="changeSku(item, val)"
            >{{ val.name }}</span
          >
        </template>
      </dd>
    </dl>
  </div>
</template>

<style scoped lang="scss">
@mixin sku-state-mixin {
  border: 1px solid #e4e4e4;
  margin-right: 10px;
  cursor: pointer;

  &.selected {
    border-color: #27ba9b;
  }

  &.disabled {
    opacity: 0.6;
    border-style: dashed;
    cursor: not-allowed;
  }
}

.goods-sku {
  padding-left: 10px;
  padding-top: 20px;

  dl {
    display: flex;
    padding-bottom: 20px;
    align-items: center;

    dt {
      width: 50px;
      color: #999;
    }

    dd {
      flex: 1;
      color: #666;

      > img {
        width: 50px;
        height: 50px;
        margin-bottom: 4px;
        @include sku-state-mixin;
      }

      > span {
        display: inline-block;
        height: 30px;
        line-height: 28px;
        padding: 0 20px;
        margin-bottom: 4px;
        @include sku-state-mixin;
      }
    }
  }
}
</style>

 

<script setup>
import { onMounted, ref } from "vue";
import axios from "axios";
import powerSet from "./power-set";
// 商品数据
const goods = ref({});
let pathMap = {};
// 数据获取完毕生成路径字典
const getGoods = async () => {
  // 1135076  初始化就有无库存的规格
  // 1369155859933827074 更新之后有无库存项(蓝色-20cm-中国)
  const res = await axios.get(
    "http://pcapi-xiaotuxian-front-devtest.itheima.net/goods?id=1369155859933827074"
  );
  goods.value = res.data.result;
  pathMap = getPathMap(goods.value);
  console.log("pathMap==", pathMap);
  // 初始化更新按钮状态
  initDisabledState(goods.value.specs, pathMap);
};
onMounted(() => getGoods());

// 切换选中状态
const changeSku = (item, val) => {
  if (val.disabled) return;
  // item 同一排的对象
  //  val 当前点击项
  if (val.selected) {
    // 如果当前是激活状态 则取消激活
    val.selected = false;
  } else {
    // 如果当前是未激活状态 则取消同排的激活状态 并激活自己
    item.values.forEach((val) => (val.selected = false));
    val.selected = true;
  }
  // 点击按钮时更新
  updateDisabledState(goods.value.specs, pathMap);
  // 产出SKU对象数据
  const index = getSelectedValues(goods.value.specs).findIndex(
    (item) => item === undefined
  );
  if (index > -1) {
    console.log("找到了,信息不完整");
  } else {
    console.log("找到了,信息完整");
    // 获取sku对象
    const key = getSelectedValues(goods.value.specs).join("-");
    console.log("key", key);
    console.log("pathMap", pathMap);
    const skuIds = pathMap[key];
    console.log("skuIds", skuIds);
    // 以 skuId 作为匹配项 去 goods.value.skus数组中找
    const skuobj = goods.value.skus.find((item) => item.id === skuIds[0]);
    console.log(skuobj);
  }
};

// 创建生成路径字典对象函数
const getPathMap = (goods) => {
  // console.log("goods.skus===", goods.skus);
  const pathMap = {};
  // 得到所有有效的sku集合
  const effectiveSkus = goods.skus.filter((sku) => sku.inventory > 0);
  // console.log("effectiveSkus===========", effectiveSkus);
  // 根据有效的sku集合使用算法得到所有的子集 [1,2] => [[1], [2], [1,2]]
  effectiveSkus.forEach((sku) => {
    // 获取可选规格值数组
    const selectedValArr = sku.specs.map((val) => val.valueName);
    // console.log("selectedValArr=====", selectedValArr); // 多个数组 格式 ['黑色', '20cm', '中国']
    //  获取可选值数组的子集
    const valueArrPowerSet = powerSet(selectedValArr);
    // console.log("valueArrPowerSet=====", valueArrPowerSet);
    /* [
  [],
  ['黑色'],
  ['20cm'],
  ['黑色', '20cm'],
  ['中国'],
  ['黑色', '中国'],
  ['20cm', '中国'],
  ['黑色', '20cm', '中国']
]
 */
    //  根据子集生成路径字典对象
    valueArrPowerSet.forEach((arr) => {
      // 根据Arr得到字符串的key,约定使用-分割 ['蓝色','美国'] => '蓝色-美国'
      const key = arr.join("-");
      // 给pathMap设置数据
      if (pathMap[key]) {
        pathMap[key].push(sku.id);
      } else {
        pathMap[key] = [sku.id];
      }
    });
    // console.log("pathMap===", pathMap);
  });
  return pathMap;
};

// 1. 定义初始化函数
// specs:商品源数据 pathMap:路径字典
const initDisabledState = (specs, pathMap) => {
  // 约定:每一个按钮的状态由自身的disabled进行控制
  specs.forEach((item) => {
    item.values.forEach((val) => {
      // 路径字典中查找是否有数据 有-可以点击 没有-禁用
      val.disabled = !pathMap[val.name];
    });
  });
};

// 获取选中匹配数组 ['黑色',undefined,undefined]
const getSelectedValues = (specs) => {
  // console.log("specs", specs);
  const arr = [];
  specs.forEach((spec) => {
    const selectedVal = spec.values.find((value) => value.selected);
    // console.log("selectedVal", selectedVal);
    // 选中为 true 保存名字 未选中 则为false 保存 undefined
    arr.push(selectedVal ? selectedVal.name : undefined);
  });
  return arr;
};

// 点击时 更新禁用状态
const updateDisabledState = (specs, pathMap) => {
  specs.forEach((spec, index) => {
    const selectedValues = getSelectedValues(specs);
    spec.values.forEach((val) => {
      selectedValues[index] = val.name;
      const key = selectedValues.filter((value) => value).join("-");
      console.log(pathMap[key]);
      if (pathMap[key]) {
        val.disabled = false;
      } else {
        val.disabled = true;
      }
    });
  });
};
</script>

<template>
  <div class="goods-sku">
    <dl v-for="item in goods.specs" :key="item.id">
      <dt>{{ item.name }}</dt>
      <dd>
        <template v-for="val in item.values" :key="val.name">
          <!-- 图片类型规格 -->
          <img
            v-if="val.picture"
            @click="changeSku(item, val)"
            :class="{ selected: val.selected, disabled: val.disabled }"
            :src="val.picture"
            :title="val.name"
          />
          <!-- 文字类型规格 -->
          <span
            v-else
            :class="{ selected: val.selected, disabled: val.disabled }"
            @click="changeSku(item, val)"
            >{{ val.name }}</span
          >
        </template>
      </dd>
    </dl>
  </div>
</template>

<style scoped lang="scss">
@mixin sku-state-mixin {
  border: 1px solid #e4e4e4;
  margin-right: 10px;
  cursor: pointer;

  &.selected {
    border-color: #27ba9b;
  }

  &.disabled {
    opacity: 0.6;
    border-style: dashed;
    cursor: not-allowed;
  }
}

.goods-sku {
  padding-left: 10px;
  padding-top: 20px;

  dl {
    display: flex;
    padding-bottom: 20px;
    align-items: center;

    dt {
      width: 50px;
      color: #999;
    }

    dd {
      flex: 1;
      color: #666;

      > img {
        width: 50px;
        height: 50px;
        margin-bottom: 4px;
        @include sku-state-mixin;
      }

      > span {
        display: inline-block;
        height: 30px;
        line-height: 28px;
        padding: 0 20px;
        margin-bottom: 4px;
        @include sku-state-mixin;
      }
    }
  }
}
</style>

别名路径联想设置

1. 根目录新建 jsconfig.json 文件

2. 添加配置项

{
    "compilerOptions": {
        "baseUrl": "./",
        "paths": {
            "@/*":["src/*"]
        }
    }
}

3. 最终效果

axios基础配置

Axios

axios 拦截器

 

import axios from "axios";

// 创建实例
const http = axios.create({
  baseURL: "http://pcapi-xiaotuxian-front-devtest.itheima.net", // 基地址
  timeout: 5000, // 超时时间
});

// 添加请求拦截器
http.interceptors.request.use(
  function (config) {
    // 在发送请求之前做些什么
    return config;
  },
  function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  }
);

// 添加响应拦截器
http.interceptors.response.use(
  function (response) {
    // 2xx 范围内的状态码都会触发该函数。
    // 对响应数据做点什么
    return response;
  },
  function (error) {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    return Promise.reject(error);
  }
);

export default http;

请求拦截器携带Token

具体使用

最终每个请求都会自动携带token 

Token失效处理

vueuse

VueUse中文文档

插槽

准备模板

<script setup>
// 接收参数
defineProps({
  msg: {
    type: String,
    required: true,
  },
});
</script>

<template>
  <div class="greetings">
    <h1 class="green">{{ msg }}</h1>
    <!-- 主体内容区域 -->
    <slot name="main" />
  </div>
</template>

<style scoped>
</style>

渲染模板

<script setup>
import HelloWorld from "@/components/HelloWorld.vue";
</script>

<template>
  <!-- 插槽 -->
  <HelloWorld msg="You did it!">
    <template #main>
      <ul>
        <li>111111</li>
        <li>111111</li>
        <li>111111</li>
        <li>111111</li>
        <li>111111</li>
      </ul>
    </template>
  </HelloWorld>
</template>

<style scoped>
</style>

懒加载指令实现

图片进入视口区域,再发送请求 借助  useIntersectionObserver | VueUse中文文档

main.js中

import { useIntersectionObserver } from "@vueuse/core";

// 定义全局指令
// 懒加载指令逻辑
app.directive("img-lazy", {
  mounted(el, binding) {
    // el: 指令绑定的那个元素 img
    // binding: binding.value  指令等于号后面绑定的表达式的值  图片url
    console.log(el, binding.value);
    const { stop } = useIntersectionObserver(el, ([{ isIntersecting }]) => {
      console.log(isIntersecting);
      if (isIntersecting) {
        // 进入视口区域
        el.src = binding.value;
        // 手动停止监听
        stop();
      }
    });
  },
});

 需要懒加载的图片

<img v-img-lazy="item.picture" alt="" />

最终效果 

  

写法

vue2中

// 模板  必须有div包裹
<template>
  <div>首页</div>
</template>



// 选项式API

data() {
  return {
    数据
  }
},
methods: {
   方法
}


Vite构建的Vue3项目中

<template>
	首页
</template>



// 组合式API


数据
let sliderList = ref([]);

方法
const mouseOut = () => {
  
};

修改数据

vue2中

export default {
  data() {
    return {
      message: 'Hello, Vue!'
    };
  },
  methods: {
    updateMessage() {
      this.message = 'Updated message';
    }
  }
}

vue3中

import { ref, reactive } from 'vue';

// setup语法糖插件  解决引入问题(自动引入所需要的插件)

// 下载安装 npm i unplugin-auto-import -D    在 vite.config.js 中进行配置

export default {
  setup() {
    // 使用 ref---可以定义基本数据类型 对象
    const messageRef = ref('Hello, Vue!');

    // 使用 reactive--可以定义对象 数组
    const data = reactive({
      message: 'Hello, Vue!',
      count: 0
    });

    // 使用ref定义的数据 通过 .value 访问
    const updateMessage = () => {
      messageRef.value = 'Updated message';
    };

    // 使用 reactive 定义的数据 可直接 访问
    const updateCount = () => {
      data.count++;
    };

    return {
      messageRef,
      data,
      updateMessage,
      updateCount
    };
  }
}


ref函数将一个普通的JavaScript值封装成一个包含.value属性的响应式对象。
而reactive函数将一个普通的JavaScript对象转换为一个完全响应式的代理对象。



toRefs 

// 解构==》响应式数据

let obj = reactive({
  name:"张三",
  age:20
})
 let { name, age } = toRefs( obj )




异步组件(按需加载) ------- 提升性能

Vuex 

// store index.js
import { createStore } from "vuex";

export default createStore({
  state:{
    num:10,
    str:'这是store数据'
  },
  getters:{},
  mutations:{},
  actions:{},
  modules:{}
})


// .vue
<template>
 <div>
   {{ num }}
 </div>
</template>

<script setup>
import { useStore } from 'vuex'

let store = useStore
{/* 即时更新 */}
let num = computed ( () => { store.state.num })


</script>




Vuex 持久化存储

Pinia

 

1. 支持选项式API和组合式API写法

2.  Pinia 没有 mutations,只有 state getters actions

3.  Pinia 分模块不需要 modules(Vuex需要)

4.  TS 支持很好

5.  Pinia 体积更小(性能更好)

6.  Pinia 可以直接修改 state 数据

// store index.js

import { defineStore } from 'pinia'
import { ref } from 'vue'
import { loginAPI } from '@/apis/user'

export const useUserStore = defineStore('user', () => {
  // 1. 定义管理用户数据的state
  const userInfo = ref({})
  // 2. 定义获取接口数据的action函数
  const getUserInfo = async ({ account, password }) => {
    const res = await loginAPI({ account, password })
    userInfo.value = res.result
  }
  // 3. 以对象的格式把state和action return
  return {
    getUserInfo,
    userInfo 
  }
}, {
// 持久化
  persist: true,
})

// .vue

<script setup>
 
import { useUserStore } from '../store'

useUserStore.getUserInfo({ account, password })

</script>

快速开始 | pinia-plugin-persistedstate

单选功能

 全选功能

Logo

前往低代码交流专区

更多推荐