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

news2024/11/18 14:35:59

文章目录

    • 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上就糊掉了呜呜
祝大家学习顺利!!
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

服务器端口查询:一项至关重要的网络管理任务

在网络管理和系统维护中&#xff0c;服务器端口查询是一项至关重要的任务。服务器端口是网络通信的入口点&#xff0c;它们允许各种服务和应用程序在网络上进行交互。因此&#xff0c;准确而有效地查询服务器端口的状态和配置对于确保网络的安全性和稳定性至关重要。 首先&…

手写电纸书天花板,阅读办公新体验 | 汉王手写电纸本 N10 2024 版使用评测

手写电纸书天花板&#xff0c;阅读办公新体验 | 汉王手写电纸本 N10 2024 版使用评测 请问如果说到电纸书&#xff0c;你的认知还只是Kindle吗&#xff1f;然而遗憾的是&#xff0c;Kindle亦是过去&#xff0c;智能才是未来。 哈喽小伙伴们好&#xff0c;我是Stark-C~&#x…

百度页面奔跑的白熊html、css

一、相关知识-动画 1.基本使用&#xff1a;先定义再调用 2. 调用动画 用keyframes定义动画&#xff08;类似定义类选择器&#xff09; keyframes动画名称{ 0%{ width:100px&#xff1b; } 100%{ width:200px; } } 使用动画 div { width:200px; height:200px; background-…

【linux】如何优雅的使用vim编辑器

基本指令 【linux】详解linux基本指令-CSDN博客 【linux】详解linux基本指令-CSDN博客 vim的基本概念 vim有很多模式&#xff0c;小编只介绍三种就能让大家玩转vim了&#xff0c; 分别是&#xff1a; 正常/普通/命令模式 插入模式 末行/底行模式 命令模式 控制屏幕光标的…

软件性能测试有哪些测试类型和方法?

软件性能测试是一种通过模拟真实用户使用情况&#xff0c;评估软件系统在各种压力和负载下的表现的测试方法。在今天这个讲究效率的时代&#xff0c;软件性能测试是不可或缺的一环。它能帮助开发人员和企业发现潜在的性能问题&#xff0c;提前优化改进&#xff0c;保证软件系统…

IS-IS开销值和协议优先级

原理概述 IS-IS 协议为路由器的每个 IS-IS 接口定义并维护了一个 Level-1开销值和一个 Level-2开销值。开销值可以在接口上或者全局上手动配置&#xff0c;也可以使用 Auto-Cost 自动计算确定。开销值的优先顺序为&#xff1a;接口上手动配置的开销值&#xff0c;全局上手动配置…

鸿蒙开发接口图形图像:【@ohos.display (屏幕属性)】

屏幕属性 屏幕属性提供管理显示设备的一些基础能力&#xff0c;包括获取默认显示设备的信息&#xff0c;获取所有显示设备的信息以及监听显示设备的插拔行为。 说明&#xff1a; 开发前请熟悉鸿蒙开发指导文档&#xff1a; gitee.com/li-shizhen-skin/harmony-os/blob/master/…

WhaleOps核心产品亮相全球AWS Marketplace,云原生实力再升级!

近日&#xff0c;开源原生DataOps商业公司WhaleOps宣布&#xff0c;其两款核心产品WhaleScheduler和WhaleTunnel现已正式上线AWS Marketplace。这将为这两款产品的全球用户带来更为便捷和高效的云服务体验&#xff0c;欢迎免费试用&#xff01; AWS Marketplace地址&#xff…

【算法】dd爱转转

✨题目链接&#xff1a; dd爱旋转 ✨题目描述 读入一个n∗n的矩阵&#xff0c;对于一个矩阵有以下两种操作 1:顺时针旋180 2:关于行镜像 如 变成 给出q个操作&#xff0c;输出操作完的矩阵 ✨输入描述: 第一行一个数n(1≤n≤1000)&#xff0c;表示矩阵大小 接下来n行&#xff…

【软考】下篇 第19章 大数据架构设计理论与实践

目录 大数据处理系统架构特征Lambda架构Lambda架构介绍Lambda架构实现Lambda架构优缺点Lambda架构与其他架构模式对比 Kappa架构Kappa架构介绍Kappa架构实现Kappa架构优缺点 常见Kappa架构变形&#xff08;Kappa、混合分析系统&#xff09;Kappa架构混合分析系统的Kappa架构 La…

快消终端门店真实性新玩法:全流程校验+多元认证多重保障

在某饮品企业会议室&#xff0c;气氛凝重。城市经理一脸严肃地扫视着团队成员&#xff0c;小李、小张和小陈等人在这锐利的目光下显得有些局促不安。 城市经理沉声开口&#xff1a;小李上报的“幸福超市”’新店在XX大街上并不存在。这是怎么回事&#xff1f; 小李支吾着回答…

番外篇 | YOLOv8改进之更换主干网络MobileNetv3 + 添加CA注意力机制

前言:Hello大家好,我是小哥谈。MobileNetv3是一种轻量级网络,采用了深度可分离卷积等轻量化技术,具有较小的模型参数量和计算复杂度,适合在计算能力较弱的设备上运行。本节课就让我们结合论文来对YOLOv8进行组合改进(更换主干网络MobileNetv3 + 添加CA注意力机制),希望…

c++(五)

c&#xff08;五&#xff09; 继承基类和派生类继承的格式继承的方式三种:public、private、protected 继承的规则多层继承多重继承 类与类的关系 继承 一个新类从已有的类那里获得其已有特性(属性、行为)&#xff0c;这种现象称为类的继承 基类和派生类 <1>从已有的类…

linux开发之设备树基本语法二

设备树特殊节点,对节点定义别名,chosen节点用来uboot给内核传参 上面的mmc0就是sdmmc0节点的别名 device_type属性 只对cpu节点和memory节点进行描述 自定义属性 这部分自定义,比如定义管脚标号,初始数值等 为什么我们可以在设备树上自己定义属性呢?设备树文件描述的是硬…

SQL数据分析常用函数

SQL 中有许多常用的函数&#xff0c;可以用于处理和操作数据。以下是一些常见的SQL 函数&#xff1a; 1. 字符串函数&#xff1a; CONCAT(str1, str2, …): 用于把多个文本字符串合并成一个长字符串(参数中有null时返回null)。 select concat(一起,学, SQL); -- 输出结果:一…

Golang | Leetcode Golang题解之第102题二叉树的层序遍历

题目&#xff1a; 题解&#xff1a; func levelOrder(root *TreeNode) [][]int {ret : [][]int{}if root nil {return ret}q : []*TreeNode{root}for i : 0; len(q) > 0; i {ret append(ret, []int{})p : []*TreeNode{}for j : 0; j < len(q); j {node : q[j]ret[i] …

P7-P9【分配器】【源文件】【OOPvs.GP】

分配器 如何分配&#xff0c;如何释放 源文件 标准库源代码文件VC布局 标准库源代码文件GCC布局 OOP(面向对象编程) VS GP(泛型编程) 这两种编程的区别&#xff1a; 面向对象编程是将数据和方法联系在一起&#xff0c;更注重对不同的对象做出不同的响应&#xff0c;更适合…

关于NLTK

一、NLTK简介 下图来自NLTK官网&#xff1a;https://www.nltk.org/index.html NLTK&#xff0c;全称为Natural Language Toolkit&#xff0c;是一个用于处理和分析自然语言文本的Python库。它提供了一系列丰富的工具和资源&#xff0c;包括词汇资源&#xff08;如WordNet&am…

电脑无法远程桌面连接,关于电脑无法建立远程桌面连接的问题分析与解决方案

在信息化快速发展的今天&#xff0c;远程桌面连接已成为许多企业和个人用户进行远程办公、技术支持以及数据管理的必备工具。然而&#xff0c;当电脑无法建立远程桌面连接时&#xff0c;可能会对用户的工作和日常生活造成极大的不便。本文将深入分析电脑无法远程桌面连接的原因…

探索超构光子学与人工智能相结合的下一代研究趋势

欢迎关注GZH《光场视觉》 一个研究小组在《固体与材料科学当前观点》&#xff08;Current Opinion in Solid State and Materials Science&#xff09;杂志上发表了一篇论文&#xff0c;强调了将超构 光子学研究与人工智能相结合的下一代研究趋势。超透镜引发了光学领域的一场…