# 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 ``` ## 修改 `SdyHeader` 组件的插槽设置 - 改成三个插槽限制 ```html ``` ## 书写首页的 顶部部分 - 样式穿透 + 样式穿透, 让该样式离开当前组件 + 写了一个 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() ```