小兔鲜Vue3 项目学习笔记Day02

系列文章目录

小兔鲜Vue3 项目学习笔记 Day01

小兔鲜Vue3 项目学习笔记 Day02

小兔鲜Vue3 项目学习笔记 Day03

小兔鲜Vue3 项目学习笔记 Day04

小兔鲜Vue3 项目学习笔记 Day05

小兔鲜Vue3 项目学习笔记 Day06

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

  • 29
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值