# vue3-options-pet
**Repository Path**: zhao-jingtao-l/vue3-options-haigou
## Basic Information
- **Project Name**: vue3-options-pet
- **Description**: 基于Vue3的选项式api c端 精致宠物商城项目
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 0
- **Created**: 2022-12-23
- **Last Updated**: 2025-06-18
## Categories & Tags
**Categories**: Uncategorized
**Tags**: vue3, Vue-pinia, vue-router, Vant, Axios
## README
# Vue3-options-精致宠物 项目细节
## 创建项目
- 打开命令行
- 输入指令
  ```shell
  $ npm init vue@latest
  ```
## 开启 `defineProps` 设置默认值的语法糖
- 在 `vite.config.ts` 文件中进行修改
  ```js
  export default defineConfig({
      plugins: [vue({
          reactivityTransform: true
      })],
      resolve: {
          alias: {
              '@': fileURLToPath(new URL('./src', import.meta.url))
          }
      }
  })
  ```
## 下载 `sass` 包
- 如果你需要在项目中使用 `sass` 语法
- 下载 `sass` 包
  ```shell
  $ npm install sass -D
  ```
## 准备一个全局 `scss` 文件
- 在 `src` 目录下新建 `styles` 文件夹
- 创建 `index.scss` 文件, 用于书写全局 样式
- **在 `main.ts` 内引入**
  ```js
  // 导入全局样式
  import '@/styles/index.scss'
  ```
## 配置 `git` 环境
- 打开命令行, 切换目录到项目根目录
- 输入指令: `$ git init`
- 进行第一次提交
## 配置一个 `UI` 组件库
- 一个移动端 `UI` 组件库 `vant`
- 下载 `vant` 组件库
  ```shell
  $ npm install vant
  ```
- 挂载到自己的项目内
- `main.ts`
  ```js
  // 导入全局样式
  import '@/styles/index.scss'
  // 导入 vant 组件库
  import vant from 'vant'
  // 把 vant 挂载到 app 实例身上
  app.use(vant)
  ```
- 适配一下移动端事件
  ```shell
  $ npm i @vant/touch-emulator -S
  ```
  - `main.ts`
  ```js
  import '@vant/touch-emulator'
  ```
- 配置 `iOs` 无法识别事件
  - 在 `body` 标签上添加 `ontouchstart=""`
- 配置全局主题
  - `App.vue`
  ```html
  
    
    
    
  
  
  ```
  - `config/theme.ts`
  ```ts
  import type { ConfigProviderProps } from 'vant'
  export const themeVars: ConfigProviderProps['themeVars'] = {
    rateIconFullColor: '#07c160',
    sliderBarHeight: '4px',
    sliderButtonWidth: '20px',
    sliderButtonHeight: '20px',
    sliderActiveBackgroundColor: '#07c160',
    buttonPrimaryBorderColor: '#07c160',
    buttonPrimaryBackgroundColor: 'skyblue',
    buttonSuccessBorderColor: 'orange',
    buttonSuccessBackgroundColor: 'orange',
  }
  ```
## 开始书写布局结构
- 按照移动端基础布局开始书写
  - 顶部固定
  - 底部固定
  - 中间自适应(溢出滚动)
## 开始书写底部导航条
- 使用 `vant` 组件库内的结构书写
- 配置好路由就可以了
  ```js
  {
    // 找不到路由
    path: '/not-found',
    name: 'not-found',
    component: NotFound
  },
  {
    // 匹配任意内容
    path: '/:pathMatch(.*)*',
    redirect: '/not-found'
  }
  ```
## `not-found` 的时候, 不显示底部导航条
- 只有我配置好的路由才会显示 `底部导航`
- 如果是意外路由, 不需要显示 `底部导航`
  + 方式: 给 `SdyFooter` 加一个 `v-if` 指令
  + 通过该指令控制 `底部导航` 加载不加载
- 问题: `v-if` 的值从哪里来
  + 我们在配置路由的时候, 可以配置一个 `路由元信息`
  + 在配置路由的时候给出的初始信息
  ```js
  {
    path: '/xxx',
    name: 'xxx',
    component: xxx,
    meta: { } // 直接在切换到当前路由的时候, 给到当前路由的信息
  }
  ```
- 需求: 有多个路由需要 `底部导航`, 只有一个路由不需要 `底部导航`, 你准备如何书写这个信息
  + 方式1:
    - 谁有, 设置一个变量为 true
    - 谁没有, 设置一个变量为 false
  + 方式2:(更好)
    - 谁有, 设置一个变量为 false
    - 谁没有, 设置一个变量为 true
- 如何在当前路由内拿到路由元信息 (meta)
  + `Vue2` : `this.$route.meta`
  + `Vue3` : 我们没有 `this`
    - 如何拿到 `$route`
    - 可以直接在 `vue-router` 第三方内解构出来一个 `useRoute`
    ```js
    import { useRoute } from 'vue-router'
    const $route = useRoute()
    ```
  + `App.vue`
    ```html
    
    ```
## 顶部导航条的二次封装
- 为了各个组件内导航条不一样
- 我们把 导航条放在了 `container` 组件内部
  + 我们需要在每一个组件内 `导入` / `使用`
  + 想法: 把 `header` 注册为全局组件
  ```js
  // 注册全局组件
  app.component('SdyHeader', SdyHeader)
  ```
- 换一种高级的方式注册组件
  ```js
  app.use(xxxx)
  ```
  + `app.use` 的原理
    - 需要传递的参数是一个 `对象`
    - 该 `对象` 内必须要有一个 `install` 方法
    - `app.use` 其实就是在执行该 `install` 方法
    - 并且把 `app` 实例传递给 `install` 方法
- 自己封装组件插件
  - `src/utils/plugins.ts`
  ```ts
  import SdyHeader from '@/views/SdyHeader/index.vue'
  import type { App } from 'vue'
  export default {
    install (app: App) {
      app.component('SdyHeader', SdyHeader)
    }
  }
  ```
  - `main.ts`
  ```js
  // 导入自己的插件库
  import Plugin from '@/utils/Plugin'
  // 挂载自己的插件
  app.use(Plugin)
  ```
## Teleport
- 作用: 把组件内的一部分 `DOM` 结构拿到外边一个指定位置
- 使用:
  ```html
  
    
  
  ```
  ```html
  
  
    
    
      a
    
    b
  
  ```
## 修改 `SdyHeader` 组件的插槽设置
- 改成三个插槽限制
  ```html
  
    
      
    
    
      {{ title }}
    
    
      
    
  
  ```
## 书写首页的 顶部部分
- 样式穿透
  + 样式穿透, 让该样式离开当前组件
  + 写了一个 scoped 是为了让样式在自己组件内生效
  + 但是有些内容不在当前组件内, 我想让他离开当前组件, 在全局生效
  + 方式1:
    - 取消 `style` 标签身上的 `scoped` 属性
    - 所有的样式都公开了
  + 方式2:
    - 让该样式穿透出去
    - 使用 `::v-deep` 伪类作为父级
    ```css
    ::v-deep .van-nav-bar__content {
      background-color: $globalColor;
    }
    ```
    - 该样式会穿透出去
    - 最新版的 `vue` 要求你使用 `:deep(你要穿透的选择器)`
    ```css
    :deep(.van-nav-bar__content) {
      background-color: $globalColor;
    }
    ```
## 请求数据
- 需要用到的第三方 `axios`
  + 对 `axios` 进行实例封装, 为了可以配置基准地址
- 准备一个文件 `urils/request.ts`
- 封装 `api` 接口
## 首页 轮播图 组件
## 首页 nav 导航组件
## 首页 秒杀/热门 组件
## 服务器挂了
- 当服务器出现问题了, 导致数据不能回来的时候
- 来到我们封装的 `request.ts` 文件内的响应拦截器内做一些事情
  ```js
  // 响应拦截器
  // 需要在本次请求失败的回调函数内做一些事情
  instance.intercaptors.response.use(成功的回调函数, 失败的回调函数)
  ```
- 利用响应拦截器, 对失败的请求做出一些处理
  ```js
  // request.ts
  instance.interceptors.response.use(
    response => {
      return response.data
    },
    err => {
      console.log('本次请求失败了', err)
      Dialog.alert({ title: '网络错误', message: '网络不给力, 稍后再来' })
    }
  )
  ```
## 点击跳转到 `search 路由`
- 给 `van-search` 组件添加一个 `focus` 事件
  + 当该文本框聚焦的时候触发
  + 使用编程式导航进行路由跳转
- 问题: 我需要拿到 `Vue2` 的时候那个 `$router` 的东西才可以
  + 从 `vue-router` 内拿到 `useRouter` 的函数
  + 通过执行这个 `useRouter` 函数得到原先的 `$router`
  ```js
  import { useRouter } from 'vue-router'
  const $router = useRouter()
  ```
## `Search` 组件书写
- 我们准备了两个小组件
  + `SdySearchHistory`
  + `SdySearchList`
- 目前想到的方案
  + 使用 `v-if` 指令
  + 通过判断 `search` 搜索框是否被写入了文本
  + 决定哪一个组件显示哪一个组件隐藏
  ```html
  
  
  
  
  ```
- 换一个高级的解决方式
  + 动态组件
  + `Vue` 里面给我们提供了一个 `component` 的组件
    - 是一个组件, 但是不知道是什么
  + 通过该组件身上的一个 `is` 属性, 来决定这个位置渲染什么内容
## 首页的 商品列表
- 问题1: 数据
  + 接口提供
- 问题2: 布局
  + `scss` 自己书写
- 问题3: 瀑布流
  + `vant` 组件库
  + 使用 `List` 组件
  + 包含 下拉刷新 和 上划加载更多(瀑布流)
## 商品分类页面
- 左边侧边栏的滑动
- 方式1:
  + 父级盒子定高 `100%`
    - `overflow: auto`
  + 侧变量超出去
  + 问题:
    - 没有回弹效果
- 方式2:
  + 需要自己来绑定事件来完成
  + `touchstart` 事件
    - 记录下光标的初始位置
    - 记录元素初始位置
  + `touchmove` 事件
    - 随着光标的移动, 实时拿到光标的坐标位置
    - 用实时拿到的坐标位置 减去 初始位置
    - 得到的是本次光标移动的距离
    - 给 `inner` 元素进行赋值
    - 问题: 直接拿到移动距离赋值给 `inner` ?
    - 赋值: 元素本身的位置 + 本次的移动距离
  + `touchend` 事件
    - 光标抬起来的时候, 让数值回归到 0
  + 上方回弹
    - 可以移动的最大值就是 `100`
      - 在移动的过程中, 判断, 如果该值超过 100, 不能再继续增加了
    - 当光标离开元素的时候
      - 只要该值 >= 0, 我们就把值设置为 0
  + 下方回弹
    - 可以移动的最大值就是 `inner高度 - slide高度 - 100`
## 登录
- 按照接口文档逐步完成
## Pinia
- `vuex` 的进阶封装版
- 定义一个全局共享的数据
  + 导入 `pinia`, 在 `pinia` 内拿到 `defineStore` 方法
  + `defineStore` 是专门创建仓库使用的
  + 语法:
    ```js
    const 变量 = defineStore('名字', () => {
      // 按照 vue3 的语法去定义各种 响应式数据 计算属性 方法 ...
      // 别忘了最后导出即可
    })
    ```
- 在组件内使用共享的数据
  + 导入指定文件内导出的仓库即可
  ```js
  // a.vue
  import { listStore } from '@/stores/xxxx.ts'
  // 直接使用 listStore
  const store = listStore()
  ```