【B站 heima】小兔鲜Vue3 项目学习笔记Day02

文章目录

    • Pinia
      • 1.使用
      • 2. pinia-计数器案例
      • 3. getters实现
      • 4. 异步action
      • 5. storeToRefsx 数据解构保持响应式
      • 6. pinia 调试
    • 项目起步
      • 1.项目初始化和git管理
      • 2. 使用ElementPlus
      • 3. ElementPlus 主题色定制
      • 4. axios 基础配置
      • 5. 路由设计
      • 6. 静态资源初始化和 Error lens安装
      • 7.scss自动导入
      • 8. Layout静态模板结构搭建
      • 9. Layout字体图标引入
      • 10.Layout一级导航渲染
      • 11. layout - 吸顶导航
      • 12. layout - Pinia优化重复请求
    • 小结

Pinia

1.使用

vue专属状态管理库,vuex替代

优势:

  • 提供了更简单的API ,去掉了mutation
  • 提供了组合式API
  • 去掉了modules,每个store都是独立的模块
  • 搭配TS一起使用提供可靠的类型判断

Pinia添加到vue项目中:

  • 创建一个新vue项目: create init vue@latest,装依赖,项目跑起来
  • 打开pinia官方文档,是个小菠萝。点击开始,有个安装选项

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 我是使用npm安装:npm install pinia

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 按照文档使用 pinia
    在这里插入图片描述
  • 在项目中实际应用(记不住看文档使用即可

在这里插入图片描述

2. pinia-计数器案例

看官方文档的基础实例学习如何使用

找和vue3语法相似的语法进行使用

在这里插入图片描述

  • 创建一个 store( state+action )

src添加一个stores文件夹,新建文件counter.js

//counter.js
// 导入一个方法 defineStore
import { defineStore } from 'pinia'
import {ref} form 'vue'

// 参数:标识 回调函数
//!!变量名字需保持规范:use+函数名 
//useCounterStore是一个方法,需执行才能得到真是store实例对象
export const useCounterStore = defineStore('counter', () => {
    //1.定义数据state
    const count = ref(0)
    // 2.定义修改数据的方法(action 同步+异步)
    const increment = () => {
        count.value++
    }
    // 3.以对象的方式return供组件使用
    return {
        count,
        increment
    }
})

  • 组件使用 store
<script setup>
//1.导入use 打头的方法
import { useCounterStore } from './stores/counter';
//2.执行方法获得store实例对象
const counterStore = useCounterStore();
// console.log(counterStore)  打印看看里面是否有count和increment
</script>

<template>
  <button @click="counterStore.increment">{{ counterStore.count }}</button>
</template>

3. getters实现

pinia中的getters直接使用computed函数进行模拟

//counter.js
// 导入一个方法 defineStore
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'

//定义并暴露一个函数useCounterStore 参数:标识 回调函数
export const useCounterStore = defineStore('counter', () => {
    //1.定义数据state
    const count = ref(0)
    // 2.定义修改数据的方法(action 同步+异步)
    const increment = () => {
        count.value++
    }
    // --  --  getters实现  --   --
    const doubleCount = computed(() => count.value * 2)
    // 3.以对象的方式return供组件使用
    return {
        count,
        increment,
        doubleCount
    }
})

这时useCountStore中就有了doubleCount这个方法了

<!--App.vue-->
<template>
  <button @click="counterStore.increment">{{ counterStore.count }}</button>
  {{ counterStore.doubleCount }}
</template>

在这里插入图片描述

4. 异步action

action中实现异步和组件中定义数据和方法的风格完全一致

安装axios: npm install axios

举个获取数据列表 的栗子,获取数据接口地址:http://geek.itheima.net/v1_0/channels

//counter.js

const list = ref([])  //存放列表数据
//异步action
    const getList = async () => {
        const res = await axios.get('http://geek.itheima.net/v1_0/channels');
    }
    
 //返回,让组件可以拿到
    return{
        list,
        getList
    }
<script setup>
//1.导入use 打头的方法
import { onMounted } from 'vue';
import { useCounterStore } from './stores/counter';
//2.执行方法获得store实例对象
const counterStore = useCounterStore();
// console.log(counterStore)
onMounted(() => {
  //获取数据
  counterStore.getList()
})
</script>

看一下网页的网络

在这里插入图片描述

给list赋值

//异步action
    const getList = async () => {
        const res = await axios.get('http://geek.itheima.net/v1_0/channels');
        list.value = res.data.data.channels
    }

渲染在页面上,使用v-for

<template>
  <button @click="counterStore.increment">{{ counterStore.count }}</button>
  {{ counterStore.doubleCount }}
  <ul>
    <li v-for="item in counterStore.list" :key="item.id">{{ item.name }}</li>
  </ul>
</template>

效果:
在这里插入图片描述

5. storeToRefsx 数据解构保持响应式

辅助保持数据(state+getter)的响应式解构

方法可以正常解构赋值哈

const {count,doubleCount} = counterStore

这样解构是不可以的,会造成响应式丢失,也就是数据变化页面不会更新。

我们可以这样写:

const {count,doubleCount} = storeToRefs(counterStore);

6. pinia 调试

使用之前使用的devtools调试工具

在这里插入图片描述
在这里插入图片描述

项目起步

1.项目初始化和git管理

创建并打开,将项目运行起来(按照绿色的来做):
在这里插入图片描述

这样说明成功

在这里插入图片描述

下面我们看一下 小兔鲜 需要哪些基础目录,

我们按照下面的图片在刚创建好的项目中创建文件夹。

componsables组合函数文件夹:存放通用的函数

使用git管理项目,手动初始化

执行命令并完成手动提交

git init
git add .
git commit -m "init"

配置别名路径联想提示

编写代码,一旦输入@/vscode会立刻联想出src所有的子目录和文件,统一文件路径,不容易出错。

步骤:1.根目录新增jsconfig.json文件

​ 2.添加配置项
在这里插入图片描述

2. 使用ElementPlus

我们在这个项目中使用了通用性组件,由ElementPlus提供

步骤:安装 - 按需引入 - 测试组件

看文档

在这里插入图片描述

安装elementPlus:npm install element-plus --save

安装两个插件:npm install -D unplugin-vue-components unplugin-auto-import

安装之后我们来依照文档配置这两个插件

//vite.config.js

//按需导入element Plus插件
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [  //插件配置文件
    vue(),
    //elementPlus插件
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],

配置文件写好后,重启项目

做个测试,看看组件能不能使用

<template>
  <el-button type="primary">elementPlus</el-button>
</template>

在这里插入图片描述

生效就OK

3. ElementPlus 主题色定制

小免鲜主题色和elementPlus默认的主题色存在冲突

通过定制主题让elementPlus的主题色和小兔鲜项目保持一致

步骤

  • 安装sass:npm i sass -D
    在这里插入图片描述

  • 准备定制文件 :styles/element/index.scss

/* 只需要重写你需要的即可 */
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
  $colors: (
    'primary': (
      // 主色
      'base': #27ba9b,
    ),
    'success': (
      // 成功色
      'base': #1dc779,
    ),
    'warning': (
      // 警告色
      'base': #ffb302,
    ),
    'danger': (
      // 危险色
      'base': #e26237,
    ),
    'error': (
      // 错误色
      'base': #cf4444,
    ),
  )
)
  • ElementPlus样式进行覆盖:通知Element使用scss语言,自动导入定制的scss文件覆盖。
//vite.config.js

import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

//按需导入element Plus插件
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [  //插件配置文件
    vue(),
    //elementPlus插件
    AutoImport({
      // 1.配置elementPlus采用sass样式配色系统
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver({ importStyle: 'sass' })],
    }),
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  },
  css: {
    preprocessorOptions: {
      scss: {
        //2.自动导入定制化样式文件进行样式覆盖
        additionalData: `@use "@/styles/element/index.scss" as *;`
      }
    }
  }
})

在这里插入图片描述

4. axios 基础配置

安装:npm i axios

配置基础实例(统一接口实例)

在这里插入图片描述

utils创建一个http.js

//axios基础封装
import axios from "axios";

const httpInstance = axios.create({
    baseURL: 'http://pcapi-xiaotuxian-front-devtest.itheima.net',
    timeout: '5000'           //5s
})

//拦截器,默认先这样写着,后面有需求再配置
// axios请求拦截器
instance.interceptors.request.use(config => {
    return config
}, e => Promise.reject(e))

// axios响应式拦截器
instance.interceptors.response.use(res => res.data, e => {
    return Promise.reject(e)
})

export default httpInstance

扩展:如果项目里面不同的业务模块需要的接口基地址不同,该如何来做?

答:axios.create()方法可以执行多次,每次执行就会生成一个新
的实例

const http1 = axios.create({baseURL:'url1'})
const http1 = axios.create({baseURL:'url2'})

5. 路由设计

  • 设计首页和登录页的路由(一级路由)

    路由设计规则:找内容切换的区域,如果是页面整体切换,则为一级路由

eslintrc.cjs配置,避免命名报错:

/* eslint-env node */
module.exports = {
  root: true,
  'extends': [
    'plugin:vue/vue3-essential',
    'eslint:recommended'
  ],
  parserOptions: {
    ecmaVersion: 'latest'
  },
  rules: {
    'vue/multi-word-component-names':0, //不再强制要求组件命名
  }
}

删除views文件夹下的组件,创建两个新文件夹LoginLayout分别创建一个index.vue文件,写入一些代码。

<template>
<h2>我是注册页/首页</h2>
</template>

打开router文件夹的index.js,删掉默认的代码。导入loginlayout组件,在routes中配置path、component属性

import { createRouter, createWebHistory } from 'vue-router'
import Login from '@/views/Login/index.vue'
import Layout from '@/views/Layout/index.vue'
// createRouter:创建router实例对象
// createWebHistory:创建history模式的路由

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      component: Layout
    },
    {
      path: '

App.vue中写入一级路由出口组件

<script setup>
import { RouterLink, RouterView } from 'vue-router'

</script>

<template>
  <!-- 一级路由出口组件 -->
  <RouterView />
</template>

项目运行效果:

在这里插入图片描述

  • 设计分类页和默认Home页路由(二级路由)

路由设计原则:找内容切换的区域,如果是在一级路由页的内部切换,则为二级路由

和上面一样,在views新增两个文件夹,一个Home,一个Category,分别创建一个index.vue,随便写点内容

//router index.js
import { createRouter, createWebHistory } from 'vue-router'
import Login from '@/views/Login/index.vue'
import Layout from '@/views/Layout/index.vue'
import Home from '@/views/Home/index.vue'
import Category from '@/views/Category/index.vue'
// createRouter:创建router实例对象
// createWebHistory:创建history模式的路由

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      component: Layout,
      children: [
        {
          path: '',
          component: Home
        },
        {
          path: 'category',
          component: Category
        }
      ]
    },
    {
      path: '/login',
      component: Login
    }
  ]
})

export default router

这两个二级路由要在Layout组件里给准备路由出口

<!--Layout index.vue-->
<template>
    <h2>我是首页</h2>
    <!-- 二级路由出口 -->
    <RouterView />
</template>

效果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

6. 静态资源初始化和 Error lens安装

图片资源 - 把images文件夹放到assets目录下

样式资源 - 把common.scss文件放到styles目录下(这个文件在资源里面,自己拿)。

main.js中引入common.scss

//main.js
//引入初始化样式文件
import '@/styles/common.scss'

error lens是一个实时提供错误警告信息的VScode插件,方便开发,在扩展程序里搜索然后安装就可以了。

7.scss自动导入

在项目里一些组件共享的色值会以scss变量的方式统一放到一个名为var.scss 的文件中。

正常组件中使用,需要先导入scss文件,再使用内部的变量,比较繁琐,自动导入可以免去手动导入的步骤,直接使用内部的变量

配置步骤:

  • 新增一个var.scss文件,存入色值变量
$xtxColor: #27ba9b;
$helpColor: #e26237;
$sucColor: #1dc779;
$warnColor: #ffb302;
$priceColor: #cf4444;
  • 通过vite.config.js配置自动导入文件
 css: {
    preprocessorOptions: {
      scss: {
        //2.自动导入定制化样式文件进行样式覆盖
        additionalData: `
        @use "@/styles/element/index.scss" as *;
        @use "@/styles/var.scss" as *;
        `,
      }
    }
  }

8. Layout静态模板结构搭建

在这里插入图片描述

Layout文件夹创建一个components文件夹,创建LayoutFooter.vue、LayoutHeader.vue、LayoutNav.vue组件。

<!--LayoutNav.vue-->
<script setup>

</script>

<template>
  <nav class="app-topnav">
    <div class="container">
      <ul>
        <template v-if="true">
          <li><a href="javascript:;""><i class="iconfont icon-user"></i>周杰伦</a></li>
          <li>
            <el-popconfirm title="确认退出吗?" confirm-button-text="确认" cancel-button-text="取消">
              <template #reference>
                <a href="javascript:;">退出登录</a>
              </template>
            </el-popconfirm>
          </li>
          <li><a href="javascript:;">我的订单</a></li>
          <li><a href="javascript:;">会员中心</a></li>
        </template>
        <template v-else>
          <li><a href="javascript:;">请先登录</a></li>
          <li><a href="javascript:;">帮助中心</a></li>
          <li><a href="javascript:;">关于我们</a></li>
        </template>
      </ul>
    </div>
  </nav>
</template>


<style scoped lang="scss">
.app-topnav {
  background: #333;
  ul {
    display: flex;
    height: 53px;
    justify-content: flex-end;
    align-items: center;
    li {
      a {
        padding: 0 15px;
        color: #cdcdcd;
        line-height: 1;
        display: inline-block;

        i {
          font-size: 14px;
          margin-right: 2px;
        }

        &:hover {
          color: $xtxColor;
        }
      }

      ~li {
        a {
          border-left: 2px solid #666;
        }
      }
    }
  }
}
</style>
<!--LayoutHeader.vue-->
<script setup>

</script>

<template>
  <header class='app-header'>
    <div class="container">
      <h1 class="logo">
        <RouterLink to="/">小兔鲜</RouterLink>
      </h1>
      <ul class="app-header-nav">
        <li class="home">
          <RouterLink to="/">首页</RouterLink>
        </li>
        <li> <RouterLink to="/">居家</RouterLink> </li>
        <li> <RouterLink to="/">美食</RouterLink> </li>
        <li> <RouterLink to="/">服饰</RouterLink> </li>
      </ul>
      <div class="search">
        <i class="iconfont icon-search"></i>
        <input type="text" placeholder="搜一搜">
      </div>
      <!-- 头部购物车 -->
      
    </div>
  </header>
</template>


<style scoped lang='scss'>
.app-header {
  background: #fff;

  .container {
    display: flex;
    align-items: center;
  }

  .logo {
    width: 200px;

    a {
      display: block;
      height: 132px;
      width: 100%;
      text-indent: -9999px;
      background: url('@/assets/images/logo.png') no-repeat center 18px / contain;
    }
  }

  .app-header-nav {
    width: 820px;
    display: flex;
    padding-left: 40px;
    position: relative;
    z-index: 998;
  
    li {
      margin-right: 40px;
      width: 38px;
      text-align: center;
  
      a {
        font-size: 16px;
        line-height: 32px;
        height: 32px;
        display: inline-block;
  
        &:hover {
          color: $xtxColor;
          border-bottom: 1px solid $xtxColor;
        }
      }
  
      .active {
        color: $xtxColor;
        border-bottom: 1px solid $xtxColor;
      }
    }
  }

  .search {
    width: 170px;
    height: 32px;
    position: relative;
    border-bottom: 1px solid #e7e7e7;
    line-height: 32px;

    .icon-search {
      font-size: 18px;
      margin-left: 5px;
    }

    input {
      width: 140px;
      padding-left: 5px;
      color: #666;
    }
  }

  .cart {
    width: 50px;

    .curr {
      height: 32px;
      line-height: 32px;
      text-align: center;
      position: relative;
      display: block;

      .icon-cart {
        font-size: 22px;
      }

      em {
        font-style: normal;
        position: absolute;
        right: 0;
        top: 0;
        padding: 1px 6px;
        line-height: 1;
        background: $helpColor;
        color: #fff;
        font-size: 12px;
        border-radius: 10px;
        font-family: Arial;
      }
    }
  }
}
</style>
<!--LayoutFooter.vue-->
<template>
  <footer class="app_footer">
    <!-- 联系我们 -->
    <div class="contact">
      <div class="container">
        <dl>
          <dt>客户服务</dt>
          <dd><i class="iconfont icon-kefu"></i> 在线客服</dd>
          <dd><i class="iconfont icon-question"></i> 问题反馈</dd>
        </dl>
        <dl>
          <dt>关注我们</dt>
          <dd><i class="iconfont icon-weixin"></i> 公众号</dd>
          <dd><i class="iconfont icon-weibo"></i> 微博</dd>
        </dl>
        <dl>
          <dt>下载APP</dt>
          <dd class="qrcode"><img src="@/assets/images/qrcode.jpg" /></dd>
          <dd class="download">
            <span>扫描二维码</span>
            <span>立马下载APP</span>
            <a href="javascript:;">下载页面</a>
          </dd>
        </dl>
        <dl>
          <dt>服务热线</dt>
          <dd class="hotline">400-0000-000 <small>周一至周日 8:00-18:00</small></dd>
        </dl>
      </div>
    </div>
    <!-- 其它 -->
    <div class="extra">
      <div class="container">
        <div class="slogan">
          <a href="javascript:;">
            <i class="iconfont icon-footer01"></i>
            <span>价格亲民</span>
          </a>
          <a href="javascript:;">
            <i class="iconfont icon-footer02"></i>
            <span>物流快捷</span>
          </a>
          <a href="javascript:;">
            <i class="iconfont icon-footer03"></i>
            <span>品质新鲜</span>
          </a>
        </div>
        <!-- 版权信息 -->
        <div class="copyright">
          <p>
            <a href="javascript:;">关于我们</a>
            <a href="javascript:;">帮助中心</a>
            <a href="javascript:;">售后服务</a>
            <a href="javascript:;">配送与验收</a>
            <a href="javascript:;">商务合作</a>
            <a href="javascript:;">搜索推荐</a>
            <a href="javascript:;">友情链接</a>
          </p>
          <p>CopyRight © 小兔鲜儿</p>
        </div>
      </div>
    </div>
  </footer>
</template>

<style scoped lang='scss'>
.app_footer {
  overflow: hidden;
  background-color: #f5f5f5;
  padding-top: 20px;

  .contact {
    background: #fff;

    .container {
      padding: 60px 0 40px 25px;
      display: flex;
    }

    dl {
      height: 190px;
      text-align: center;
      padding: 0 72px;
      border-right: 1px solid #f2f2f2;
      color: #999;

      &:first-child {
        padding-left: 0;
      }

      &:last-child {
        border-right: none;
        padding-right: 0;
      }
    }

    dt {
      line-height: 1;
      font-size: 18px;
    }

    dd {
      margin: 36px 12px 0 0;
      float: left;
      width: 92px;
      height: 92px;
      padding-top: 10px;
      border: 1px solid #ededed;

      .iconfont {
        font-size: 36px;
        display: block;
        color: #666;
      }

      &:hover {
        .iconfont {
          color: $xtxColor;
        }
      }

      &:last-child {
        margin-right: 0;
      }
    }

    .qrcode {
      width: 92px;
      height: 92px;
      padding: 7px;
      border: 1px solid #ededed;
    }

    .download {
      padding-top: 5px;
      font-size: 14px;
      width: auto;
      height: auto;
      border: none;

      span {
        display: block;
      }

      a {
        display: block;
        line-height: 1;
        padding: 10px 25px;
        margin-top: 5px;
        color: #fff;
        border-radius: 2px;
        background-color: $xtxColor;
      }
    }

    .hotline {
      padding-top: 20px;
      font-size: 22px;
      color: #666;
      width: auto;
      height: auto;
      border: none;

      small {
        display: block;
        font-size: 15px;
        color: #999;
      }
    }
  }

  .extra {
    background-color: #333;
  }

  .slogan {
    height: 178px;
    line-height: 58px;
    padding: 60px 100px;
    border-bottom: 1px solid #434343;
    display: flex;
    justify-content: space-between;

    a {
      height: 58px;
      line-height: 58px;
      color: #fff;
      font-size: 28px;

      i {
        font-size: 50px;
        vertical-align: middle;
        margin-right: 10px;
        font-weight: 100;
      }

      span {
        vertical-align: middle;
        text-shadow: 0 0 1px #333;
      }
    }
  }

  .copyright {
    height: 170px;
    padding-top: 40px;
    text-align: center;
    color: #999;
    font-size: 15px;

    p {
      line-height: 1;
      margin-bottom: 20px;
    }

    a {
      color: #999;
      line-height: 1;
      padding: 0 10px;
      border-right: 1px solid #999;

      &:last-child {
        border-right: none;
      }
    }
  }
}
</style>

修改一下Layoutindex.vue

<script setup>
import LayoutNav from './components/LayoutNav.vue'
import LayoutHeader from './components/LayoutHeader.vue'
import LayoutFooter from './components/LayoutFooter.vue'
</script>

<template>
  <LayoutNav />
  <LayoutHeader />
  <RouterView />
  <LayoutFooter />
</template>

效果:

在这里插入图片描述

9. Layout字体图标引入

在这里插入图片描述

这里的图标没有引入,我们使用的是阿里的字体图标库,使用 font-class 引用的方式

在这里插入图片描述

在这里插入图片描述

将这个加入到index.html文件中

  <link rel="stylesheet" href="//at.alicdn.com/t/font_2143783_iq6z4ey5vu.css">

效果:

在这里插入图片描述

看下面这个周杰伦旁边的小人儿

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

它对应的代码如下

在这里插入图片描述

10.Layout一级导航渲染

静态结构已经全部搭建好了,我们要使用后端接口渲染 渲染一级导航路由,也就是这:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

实现步骤:

  • 根据接口文档封装接口函数
  • 发生请求获取数据列表
  • v-for渲染页面

apis文件夹下创建layout.js文件,封装接口

import httpInstance from '@/utils/http.js'

//获取目录
export function getCategoryAPI() {
    return httpInstance({
        url: '/home/category/head'
    })
}

来到LayoutHeader组件,引入接口

封装一个函数getCategory,返回的是promise对象,使用async/await

在挂载完成之后(onMounted)调用函数getCategory

打印res看一下请求的数据,定义一个响应式空数组categoryList接收后台传入的数据。

将 请求 封装进 函数 中是因为方便书写请求前后的逻辑

<script setup>
import { getCategoryAPI } from '@/apis/layout'
import { onMounted, ref } from 'vue'

const categoryList = ref([])
const getCategory = async () => {
    const res = await getCategoryAPI()
    categoryList.value = res.result
    // console.log(res)
}

onMounted(() => getCategory())

</script>

在这里插入图片描述

获取数据成功之后,使用v-for渲染数据

<ul class="app-header-nav">
                <li class="home" v-for="item in categoryList" :key="item.id">
                    <RouterLink to="/">{{ item.name }}</RouterLink>
                </li>
            </ul>

效果:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

11. layout - 吸顶导航

需求:浏览器上下滚动过程中,如果距离顶部的滚动距离大于78px,吸顶导航显示,小于78px隐藏

步骤:

  • 准备吸顶导航组件
  • 获取滚动距离
  • 滚动距离作判断条件控制组件盒子展示或隐藏

吸顶导航组件

<script setup>

</script>

<template>
  <div class="app-header-sticky">
    <div class="container">
      <RouterLink class="logo" to="/" />
      <!-- 导航区域 -->
      <ul class="app-header-nav ">
        <li class="home">
          <RouterLink to="/">首页</RouterLink>
        </li>
        <li>
          <RouterLink to="/">居家</RouterLink>
        </li>
        <li>
          <RouterLink to="/">美食</RouterLink>
        </li>
        <li>
          <RouterLink to="/">服饰</RouterLink>
        </li>
        <li>
          <RouterLink to="/">母婴</RouterLink>
        </li>
        <li>
          <RouterLink to="/">个护</RouterLink>
        </li>
        <li>
          <RouterLink to="/">严选</RouterLink>
        </li>
        <li>
          <RouterLink to="/">数码</RouterLink>
        </li>
        <li>
          <RouterLink to="/">运动</RouterLink>
        </li>
        <li>
          <RouterLink to="/">杂项</RouterLink>
        </li>
      </ul>

      <div class="right">
        <RouterLink to="/">品牌</RouterLink>
        <RouterLink to="/">专题</RouterLink>
      </div>
    </div>
  </div>
</template>


<style scoped lang='scss'>
.app-header-sticky {
  width: 100%;
  height: 80px;
  position: fixed;
  left: 0;
  top: 0;
  z-index: 999;
  background-color: #fff;
  border-bottom: 1px solid #e4e4e4;
  // 此处为关键样式!!!
  // 状态一:往上平移自身高度 + 完全透明
  transform: translateY(-100%);
  opacity: 0;

  // 状态二:移除平移 + 完全不透明
  &.show {
    transition: all 0.3s linear;
    transform: none;
    opacity: 1;
  }

  .container {
    display: flex;
    align-items: center;
  }

  .logo {
    width: 200px;
    height: 80px;
    background: url("@/assets/images/logo.png") no-repeat right 2px;
    background-size: 160px auto;
  }

  .right {
    width: 220px;
    display: flex;
    text-align: center;
    padding-left: 40px;
    border-left: 2px solid $xtxColor;

    a {
      width: 38px;
      margin-right: 40px;
      font-size: 16px;
      line-height: 1;

      &:hover {
        color: $xtxColor;
      }
    }
  }
}

.app-header-nav {
  width: 820px;
  display: flex;
  padding-left: 40px;
  position: relative;
  z-index: 998;

  li {
    margin-right: 40px;
    width: 38px;
    text-align: center;

    a {
      font-size: 16px;
      line-height: 32px;
      height: 32px;
      display: inline-block;

      &:hover {
        color: $xtxColor;
        border-bottom: 1px solid $xtxColor;
      }
    }

    .active {
      color: $xtxColor;
      border-bottom: 1px solid $xtxColor;
    }
  }
}
</style>

在Layout文件夹下的index.vue中引入这个组件,使用起来

<script setup>
import LayoutNav from './components/LayoutNav.vue'
import LayoutHeader from './components/LayoutHeader.vue'
import LayoutFooter from './components/LayoutFooter.vue'
import LayoutFixed from './LayoutFixed.vue'
</script>

<template>
    <LayoutNav />
    <LayoutHeader />
    <RouterView />
    <LayoutFooter />
    <LayoutFixed />
</template>

关键样式(LayoutFixed中):

.app-header-sticky {
    width: 100%;
    height: 80px;
    position: fixed;
    left: 0;
    top: 0;   //置顶
    z-index: 999;
    background-color: #fff;
    border-bottom: 1px solid #e4e4e4;
    
    // 此处为关键样式!!!
    // 状态一:往上平移自身高度 + 完全透明
    transform: translateY(-100%);   //平移出页面
    opacity: 0;  //透明度为0

    // 状态二:移除平移 + 完全不透明
    //想让组件显示出来只需要加上class = "show" 即可
    &.show {   
        transition: all 0.3s linear;
        transform: none;
        opacity: 1;  //完全不透明
    }

获取滚动距离,不自己写了,使用一个vueUse插件,安装一下
安装:npm i @vueuse/core

滚动使用的是useScroll,解构的这个y就是垂直方向滚动的距离。

import { useScroll } from '@vueuse/core'
const { y } = useScroll(window)

y>78时,show生效,我们使用 vue 的动态类实现方式

 <div class="app-header-sticky" :class="{ show: y > 78 }">

12. layout - Pinia优化重复请求

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们要把 吸顶导航 组件也转化成数据动态获取的,修改完后我们发现请求了两次数据

<!-- LayoutFixed -->
<script setup>
import { useScroll } from '@vueuse/core'
import { getCategoryAPI } from '@/apis/layout'
import { onMounted, ref } from 'vue'

const categoryList = ref([])  //目录数据列表
const { y } = useScroll(window)  //获取滚动距离
const getCategory = async () => {
    const res = await getCategoryAPI()
    categoryList.value = res.result
    // console.log(res)
}

onMounted(() => getCategory())


</script>

<template>
    <div class="app-header-sticky" :class="{ show: y > 78 }">
        <div class="container">
            <RouterLink class="logo" to="/" />
            <!-- 导航区域 -->
            <ul class="app-header-nav">
                <li class="home" v-for="item in categoryList" :key="item.id">
                    <RouterLink to="/">{{ item.name }}</RouterLink>
                </li>
            </ul>
            <div class="right">
                <RouterLink to="/">品牌</RouterLink>
                <RouterLink to="/">专题</RouterLink>
            </div>
        </div>
    </div>
</template>


<style scoped lang='scss'>
.app-header-sticky {
    width: 100%;
    height: 80px;
    position: fixed;
    left: 0;
    top: 0;
    z-index: 999;
    background-color: #fff;
    border-bottom: 1px solid #e4e4e4;
    // 此处为关键样式!!!
    // 状态一:往上平移自身高度 + 完全透明
    transform: translateY(-100%);
    opacity: 0;

    // 状态二:移除平移 + 完全不透明
    &.show {
        transition: all 0.3s linear;
        transform: none;
        opacity: 1;
    }

    .container {
        display: flex;
        align-items: center;
    }

    .logo {
        width: 200px;
        height: 80px;
        background: url("@/assets/images/logo.png") no-repeat right 2px;
        background-size: 160px auto;
    }

    .right {
        width: 220px;
        display: flex;
        text-align: center;
        padding-left: 40px;
        border-left: 2px solid $xtxColor;

        a {
            width: 38px;
            margin-right: 40px;
            font-size: 16px;
            line-height: 1;

            &:hover {
                color: $xtxColor;
            }
        }
    }
}

.app-header-nav {
    width: 820px;
    display: flex;
    padding-left: 40px;
    position: relative;
    z-index: 998;

    li {
        margin-right: 40px;
        width: 38px;
        text-align: center;

        a {
            font-size: 16px;
            line-height: 32px;
            height: 32px;
            display: inline-block;

            &:hover {
                color: $xtxColor;
                border-bottom: 1px solid $xtxColor;
            }
        }

        .active {
            color: $xtxColor;
            border-bottom: 1px solid $xtxColor;
        }
    }
}
</style>
<!-- LayoutHeader -->
<script setup>
import { getCategoryAPI } from '@/apis/layout'
import { onMounted, ref } from 'vue'

const categoryList = ref([])
const getCategory = async () => {
    const res = await getCategoryAPI()
    categoryList.value = res.result
    // console.log(res)
}

onMounted(() => getCategory())

</script>

<template>
    <header class='app-header'>
        <div class="container">
            <h1 class="logo">
                <RouterLink to="/">小兔鲜~</RouterLink>
            </h1>
            <ul class="app-header-nav">
                <li class="home" v-for="item in categoryList" :key="item.id">
                    <RouterLink to="/">{{ item.name }}</RouterLink>
                </li>
            </ul>
            <div class="search">
                <i class="iconfont icon-search"></i>
                <input type="text" placeholder="搜一搜">
            </div>
            <!-- 头部购物车 -->
        </div>
    </header>
</template>
<style scoped lang='scss'>
.app-header {
    background: #fff;

    .container {
        display: flex;
        align-items: center;
    }

    .logo {
        width: 200px;

        a {
            display: block;
            height: 132px;
            width: 100%;
            text-indent: -9999px;
            background: url('@/assets/images/logo.png') no-repeat center 18px / contain;
        }
    }

    .app-header-nav {
        width: 820px;
        display: flex;
        padding-left: 40px;
        position: relative;
        z-index: 998;

        li {
            margin-right: 40px;
            width: 38px;
            text-align: center;

            a {
                font-size: 16px;
                line-height: 32px;
                height: 32px;
                display: inline-block;

                &:hover {
                    color: $xtxColor;
                    border-bottom: 1px solid $xtxColor;
                }
            }

            .active {
                color: $xtxColor;
                border-bottom: 1px solid $xtxColor;
            }
        }
    }

    .search {
        width: 170px;
        height: 32px;
        position: relative;
        border-bottom: 1px solid #e7e7e7;
        line-height: 32px;

        .icon-search {
            font-size: 18px;
            margin-left: 5px;
        }

        input {
            width: 140px;
            padding-left: 5px;
            color: #666;
        }
    }

    .cart {
        width: 50px;

        .curr {
            height: 32px;
            line-height: 32px;
            text-align: center;
            position: relative;
            display: block;

            .icon-cart {
                font-size: 22px;
            }

            em {
                font-style: normal;
                position: absolute;
                right: 0;
                top: 0;
                padding: 1px 6px;
                line-height: 1;
                background: $helpColor;
                color: #fff;
                font-size: 12px;
                border-radius: 10px;
                font-family: Arial;
            }
        }
    }
}
</style>

在这里插入图片描述

stores新增category.js

import { ref } from 'vue'
import { defineStore } from 'pinia'
import { getCategoryAPI } from '@/apis/layout'
export const useCategoryStore = defineStore('category', () => {
  // 导航列表的数据管理
  // state 导航列表数据
  const categoryList = ref([])

  // action 获取导航数据的方法
  const getCategory = async () => {
    const res = await getCategoryAPI()
    categoryList.value = res.result
  }

  return {
    categoryList,
    getCategory
  }
})

使用:

Login文件夹的index.vue

<script setup>
//出发获取导航列表的action
import { useCategoryStore } from '@/stores/category.js'
import { onMounted } from 'vue'

const categoryStore = useCategoryStore()

onMounted(() => categoryStore.getCategory())
</script>

删掉(注释)LoginFixedLoginHeader中相关的代码

<!-- LayoutFixed -->
<script setup>
import { useScroll } from '@vueuse/core'
// import { getCategoryAPI } from '@/apis/layout'
// import { onMounted, ref } from 'vue'

// const categoryList = ref([])  //目录数据列表
const { y } = useScroll(window)  //获取滚动距离
// const getCategory = async () => {
//     const res = await getCategoryAPI()
//     categoryList.value = res.result
//     // console.log(res)
// }

// onMounted(() => getCategory())

// 使用pinia中的数据
import { useCategoryStore } from '@/stores/category.js'

const categoryStore = useCategoryStore()


</script>
<template>
     <ul class="app-header-nav">
                    <li class="home" v-for="item in categoryStore.categoryList" :key="item.id">
                        <RouterLink to="/">{{ item.name }}</RouterLink>
                    </li>
                </ul>
</template>
<script setup>
import { useCategoryStore } from '@/stores/category.js'
// import { getCategoryAPI } from '@/apis/layout'
// import { onMounted, ref } from 'vue'

// const categoryList = ref([])
// const getCategory = async () => {
//     const res = await getCategoryAPI()
//     categoryList.value = res.result
//     // console.log(res)
// }

// onMounted(() => getCategory())
const categoryStore = useCategoryStore()

</script>
<template>
    <ul class="app-header-nav">
                    <li class="home" v-for="item in categoryStore.categoryList" :key="item.id">
                        <RouterLink to="/">{{ item.name }}</RouterLink>
                    </li>
                </ul>
</template>

OK,没问题

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

小结

本篇文章,主要学习了Pinia管理数据,以及Layout的相关知识
私密马赛,图片有亿点糊,我是在typra上面写的,截到csdn上就糊掉了呜呜
祝大家学习顺利!!
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/640293.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

无人机+飞行服务:无人机飞防服务(打药+施肥+播种)技术详解

无人机飞防服务&#xff0c;结合了先进的无人机技术与农业实践&#xff0c;为现代农业提供了高效、精准的打药、施肥和播种解决方案。以下是对这些技术的详细解析&#xff1a; 一、无人机打药技术 无人机打药技术利用无人机搭载喷雾设备&#xff0c;对农田进行精准施药。通过…

【Numpy】深入解析numpy.mgrid()函数

numpy.mgrid()&#xff1a;多维网格生成与数值计算的利器 &#x1f308; 欢迎莅临我的个人主页&#x1f448;这里是我深耕Python编程、机器学习和自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;并乐于分享知识与经验的小天地&#xff01;&#x1f387; &#x1f393…

vuejs路由和组件系统

前端路由原理 createRouter * hash* window.addEventListener(hashChange)* 两种实现路由切换的模式&#xff1a;UI组件&#xff08;router-link&#xff0c;router-view&#xff09;&#xff0c;Api&#xff08;push()方法&#xff09; * history * HTML5新增的API &#xff0…

Qt下使用QImage和OpenCV实现图像的拼接与融合

文章目录 前言一、使用QImage进行水平拼接二、使用OpenCV进行水平拼接三、使用OpenCV进行图像融合四、示例完整代码总结 前言 本文主要讲述了在Qt下使用QImage和OpenCV实现图像的拼接与融合&#xff0c;并结合相应的示例进行讲解&#xff0c;以便大家学习&#xff0c;如有错误…

分割文本文件

分割一个.txt文件&#xff0c;可以选择在命令行中使用split指令&#xff0c;或者编写一段脚本进行操作。以下是一个简单的Python脚本来分割文本文件&#xff1a; def split_file(file, lines):# Open source filewith open(file, r) as source:count 0atEOF Falsewhile not …

齐护K210系列教程(三十四)_视觉PID巡线小车

视觉PID巡线小车 1.前言2.简介3.代码讲解3.1初始化3.2.色块查找3.3色块分析3.3.1 区域13.3.2 区域2 3.4 侦测关键点部分3.4.1正常巡线3.4.2 右转路口 3.4.3十字路口3.4. PID计算 4.完整代码5.小车端程序6.参考程序联系我们 1.前言 本课程主要讲述如何使用AIstart_k210主板完成…

SpringMVC接收请求参数的方式:

接收简单变量的请求参数 直接使用简单变量作为形参进行接收&#xff08;这里简单变量名称需要与接收的参数名称保持一致&#xff0c;否则需要加上RequestParam注解&#xff09;&#xff1a; 细节&#xff1a; 1&#xff1a;SpringMVC会针对常见类型&#xff08;八种基本类型及…

【Crypto】一眼就解密

文章目录 前言一眼就解密解题感悟 前言 Basic写累了&#xff0c;写写别的 一眼就解密 一眼md5试一试 小小flag 拿下&#xff01; 解题感悟 30秒搞定

Python第三方包安装与配置教程

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、Windows系统下的Python包安装 二、Linux系统下的Python包安装 三、配置Python环境 四…

打印9*9乘法表(递归或压缩矩阵)python

打印9*9表def print_multiplication_table(row, col):if row > 10:return # 递归结束条件if col row:print() # 换行print_multiplication_table(row 1, 1) # 递归调用下一行else:print(f"{row-1} * {col} {(row-1) * col}\t", end"") # 打印乘法…

Top3专业课150满分,怎么考的?

这个系列会邀请上岸学长学姐进行经验分享~ 今天经验分享的同学是小马哥上海交大819的全程班学员&#xff0c;专业课150分满分&#xff0c;这位同学也是819期末考试的第一名&#xff0c;非常厉害&#xff01;大家吸吸欧气&#xff01; 初试成绩单 前言 先介绍下自己&#xff0…

mysql binlog统一恢复误删数据库、表、数据(没有任何备份)

先将mysql文件夹中的my.ini进行设置 在 [mysqld]下边加上 # mysql-bin 是日志的基本名或前缀名&#xff0c;最后生成的日志文件是mysql-bin.000001类似&#xff0c;重启mysql数字会递增 log_binmysql-bin #binlog格式&#xff0c;statement&#xff0c;row&#xff0c;mixed可…

慢性乙型肝炎肝脏剪切波弹性成像的深度学习放射学显著改善了肝纤维化的诊断性能 | 文献速递-深度学习结合影像组学

慢性乙型肝炎肝脏剪切波弹性成像的深度学习放射学显著改善了肝纤维化的诊断性能 | 文献速递-深度学习结合影像组学 麦田医学 美好事物中转站 2024-05-21 11:03 Title 题目 Deep learning Radiomics of shear wave elastography significantly improved diagnostic performa…

【linux-kernel内核移植记录-踩坑以及注意事项】

目录 1. 环境介绍2.编译原厂的kernel2.1 通过tftp挂载原厂linux内核 3. 修改对应的驱动3.1 修改CPU频率3.2 修改MMC3.3 修改网络驱动 4. 总结 1. 环境介绍 ubuntu版本16.04I.MX6ULL开发板&#xff0c;阿尔法uboot正常启动&#xff0c;能ping通ubuntu&#xff0c;可通过tftpboo…

【0007day】总体标准差、样本标准差和无偏估计

文章目录 总体标准差和样本标准差无偏估计无偏性与无偏估计量 总体标准差和样本标准差 一些表示上的差别。 总体标准差 样本标准差 两者的区别 样本方差为什么除以n-1? 这主要是由于样本的方差会低估总体的方差&#xff08;抽样的过程中&#xff0c;按照概率来说&#xff0…

C++面向对象的第二大特性:继承

1.继承的介绍 首先容我先向大家举一个列子: 我这里定义了一个Person的类 class Person { protected:string name;int age;string address;}; 在这个基础上&#xff0c;我要定义一个关于Student , Worker 的类 由于Student Worker都具有Person类中的成员变量 &#xff0c…

【C语言】指针(三)

目录 一、字符指针 1.1 ❥ 使用场景 1.2 ❥ 有关字符串笔试题 二、数组指针 2.1 ❥ 数组指针变量 2.2 ❥ 数组指针类型 2.3 ❥ 数组指针的初始化 三、数组指针的使用 3.1 ❥ 二维数组和数组名的理解 3.2 ❥ 二维数组传参 四、函数指针 4.1 ❥ 函数的地址 4.2 ❥ 函数…

探索亚马逊云科技技术课程:大模型平台与提示工程的应用与优化

上方图片源自亚马逊云科技【生成式 AI 精英速成计划】技术开发技能课程 前言 学习了亚马逊云科技–技术开发技能课程 本课程分为三个部分&#xff0c;了解如何使用大模型平台、如何训练与部署大模型及生成式AI产品应用与开发&#xff0c;了解各类服务的优势、功能、典型使用案…

借助 CloudFlare 增强站点内容保护防采集

今天在一位站长的帮助下实测了 CloudFlare 增强站点内容保护实现防采集的功能,效果那是杠杠的,如果您的站点原创内容比较多的话,明月强烈建议试试 CloudFlare 这个内容保护,无论是 WordPress 、Typecho 都有非常好的效果,并且几乎没有任何误伤,搜索引擎爬虫蜘蛛更是不会影…

Adobe Animate AN v24.0.2 安装教程 (动画特效设计及合成工具)

Adobe系列软件安装目录 一、Adobe Photoshop PS 25.6.0 安装教程 (最流行的图像设计软件) 二、Adobe Media Encoder ME v24.3.0 安装教程 (视频和音频编码渲染工具) 三、Adobe Premiere Pro v24.3.0 安装教程 (领先的视频编辑软件) 四、Adobe After Effects AE v24.3.0 安装…