day-117-one-hundred-and-seventeen-20230721-vue3实战项目初步
vue3实战项目初步
创建vue3项目
清空多余的文件
- 移除 assets 资源文件
- 移除 components 目录下的所有组件
- 删除 router 中的路由配置,页面的配置
- 移除 stores 中的状态管理
- 移除 views 下的所有页面组件
- app.vue 中删除 没用的信息
- main.js 中移除导入的 css 文件
安装项目需要的vscode插件
- ESLint插件 保证代码格式是正确的,符合规范
支持pxtoRem
-
根据当前的屏幕大小/10来计算根节点的font-size
pnpm i lib-flexible # 引入样式
-
src/main.js
import 'lib-flexible'
-
-
把css文件中的px单位转成rem,具体转换比例看postcss.config.cjs。
pnpm i postcss-pxtorem -D
-
postcss.config.cjs
module.exports = { plugins: { 'postcss-pxtorem': { // rootvalue是根据设计稿来计算的 vant在设计的时候 设计稿用的是375px rootValue: 37.5, // Vant 官方根字体大小是 37.5 propList: ['*'] // 支持哪些属性的转换 } } }
- cjs表示是node环境,也可以用js,不过用js不好区分
-
-
报红,配置.eslintrc.cjs。是因为默认不支持node的CommonJS规范,只支持ESModule,而CommonJS规范2023年依旧广泛使用于node环境。而我们项目中,有一些配置相关的代码是在node环境下跑的,一般我们需要让这些在node环境下跑的代码后缀写成cjs,以便区分项目中的代码是跑在ESModule还是node。
-
.eslintrc.cjs 配置让项目环境支持node的CommonJS规范及浏览器的ES6Module规范。
module.exports = { env: { browser: true, node: true }, }
-
- 测试配置是否成功了。
-
src/App.vue 看移动端宽度为414px时,背景是否占满整个屏幕。
<script setup></script> <template> <div class="box">根组件</div> </template> <style scoped> .box { width: 375px; height: 200px; background-color: red; } </style>
-
组件按需导入
-
安装依赖
pnpm i unplugin-vue-components #组件按需导入
pnpm i vant #UI框架主体
-
配置插件需要的配置项
-
vite.config.js
//实现组件内的动态导入,将vant-ui中的组件自动注册成全局组件。 import Components from 'unplugin-vue-components/vite' import { VantResolver } from 'unplugin-vue-components/resolvers' export default defineConfig({ plugins: [vue(), vueJsx(), Components({ resolvers: [VantResolver()] })] })
-
测试普通组件
-
src/App.vue
<template> <van-button>按钮</van-button> </template>
-
-
-
配置全局属性或全局方法
-
有些组件不能直接按需导入,需要配置到全局属性或全局指令或全局方法上
-
src/installVant.js 配置一些不能直接按需导入的全局属性及全局方法,以插件的形式来写,让相关功能的代码更统一在一起。
import { showToast, showDialog, showNotify, showImagePreview, Lazyload } from 'vant' import 'vant/es/toast/style' import 'vant/es/dialog/style' import 'vant/es/image-preview/style' import 'vant/es/notify/style' export function installVant(app) { //属性 app.config.globalProperties.$showToast = showToast app.config.globalProperties.$showDialog = showDialog app.config.globalProperties.$showNotify = showNotify app.config.globalProperties.$showImagePreview = showImagePreview // 指令 app.use(Lazyload) // ... 配置vant中的指令和属性,增加相关配置 }
-
src/main.js 导入上方配置好的全局属性及指令
import { installVant } from './installVant' // 使用这行的前提是app已经创建,并且在app挂载到具体DOM前。 installVant(app)
-
-
测试是否全局属性已经按需导入了。
-
src/App.vue
<template> <button @click="this.$showToast('测试弹框')">全局属性调用的按钮</button> </template>
-
-
- 相关参考:
方法的自动导入
pnpm i unplugin-auto-import
- vite.config.js
// 用于处理自动导入vue与vue-router与pinia中的方法。
import AutoImport from 'unplugin-auto-import/vite'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
AutoImport({
imports: ['vue', 'vue-router', 'pinia'],
eslintrc: {
enabled: true // 开启后,生成eslint配置文件;
}
})
],
})
-
运行一次之后,会生成一个
/.eslintrc-auto-import.json
文件,之后把plugins->AutoImport->eslintrc.enabled为false,防止每次都重新生成。-
vite.config.js
// 用于处理自动导入vue与vue-router与pinia中的方法。 import AutoImport from 'unplugin-auto-import/vite' // https://vitejs.dev/config/ export default defineConfig({ plugins: [ AutoImport({ imports: ['vue', 'vue-router', 'pinia'], eslintrc: { enabled: false // 开启后,生成eslint配置文件; } }) ], })
-
-
用新生成文件的文件名,配置到.eslintrc.cjs中
-
.eslintrc.cjs
/* eslint-env node */ module.exports = { extends: [ '.eslintrc-auto-import.json', ], }
-
-
测试
- src/App.vue
提交规范(没具体测试-好像是nvm与node混用导致的问题)
- 让每次提交代码前都执行一次
npm run lint
;
-
每次提交前执行
npm run lint
; -
使用husky插件
git init # git仓库初始化 pnpm install husky -D # 安装husky包 npm pkg set scripts.prepare="husky install" # 设置prepare命令脚本 pnpm prepare # 执行prepare命令 npx husky add .husky/pre-commit "pnpm lint" # 添加提交钩子
设置提交信息(没具体测试-好像是nvm与node混用导致的问题)
- 对提交的注释格式要求更严格;
-
安装依赖
pnpm install @commitlint/cli @commitlint/config-conventional -D
-
添加钩子命令
npx husky add .husky/commit-msg 'npx --no-install commitlint --edit `echo "\$1"`'
-
设置插件
- commitlint.config.cjs
module.exports = { extends: ["@commitlint/config-conventional"], };
-
每次提交时,要加前缀,写的注释有要求。前缀样式如下
build 主要⽬的是修改项⽬构建系统(例如 glup,webpack,rollup 的配置等)的提交 chore 不属于以上类型的其他类型 ci 主要⽬的是修改项⽬继续集成流程(例如 Travis,Jenkins,GitLab CI,Circle 等)的提交 docs ⽂档更新 feat 新功能、新特性 fix 修改 bug perf 更改代码,以提⾼性能 refactor 代码重构(重构,在不影响代码内部⾏为、功能下的代码修改) revert 恢复上⼀次提交 style 不影响程序逻辑的代码修改(修改空⽩字符,格式 缩进,补全缺失的分号等,没有改变代码逻辑) test 测试⽤例新增、修改
修改ESLint的默认配置-配置多个单词的组件名
- 允许单个单词的组件名。
-.eslintrc.cjs
module.exports = {
rules: {
'vue/multi-word-component-names': 0
},
}
基础路由
- src/App.vue
<script setup></script>
<template>
<!-- 用来展示页面的切换 -->
<router-view></router-view>
</template>
<style scoped></style>
- src/views/Home.vue
<template>home</template>
- src/views/Category.vue
<template>Category</template>
- src/views/Cart.vue
<template>Cart</template>
- src/views/User.vue
<template>User</template>
- src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{ path: '/', redirect: '/home' },
{ path: '/home', name: 'home', component: Home },
{ path: '/category', name: 'category', component: () => import('../views/Category.vue') },
{ path: '/cart', name: 'cart', component: () => import('../views/Cart.vue') },
{ path: '/user', name: 'user', component: () => import('../views/User.vue') }
]
})
export default router
默认注册某个文件夹下的vue文件为全局组件
- vite.config.js
//实现组件内的动态导入,将vant-ui中的组件自动注册成全局组件。
import Components from 'unplugin-vue-components/vite'
import { VantResolver } from 'unplugin-vue-components/resolvers'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
Components({
dirs: ['src/components'], // 希望components下的文件夹 自动变成全局组件
resolvers: [VantResolver()]
}),
],
})
字体库
-
选择图标,选择Symbol,得到iconfont.js。
-
去除
fill=""
的所有属性,去除连为默认背景色。-
src/assets/iconfont.js
/* eslint-disable */ ;(window._iconfont_svg_string_4175143 = '<svg><symbol id="icon-home" viewBox="0 0 1024 1024"><path d="M216.40533334 308.224q-34.816 3.072-48.128 2.048-9.216 0-15.36-2.048 19.456-34.816 35.84-63.488 14.336-24.576 27.136-48.128t17.92-31.744q9.216-16.384 20.992-24.064t33.28-7.68l39.936 0 79.872 0 100.352 0 100.352 0 80.896 0 41.984 0q12.288 0 22.528 0.512t18.432 4.096 15.872 11.264 15.872 22.016l22.528 39.936 28.672 49.152q16.384 28.672 34.816 60.416-8.192 1.024-18.432 1.024-18.432 0-46.08-6.144-20.48-4.096-41.472-12.288t-35.328-8.192q-11.264 0-23.04 4.096t-25.6 8.704-29.184 8.192-31.744 3.584-32.768-4.096-32.256-8.704-30.72-8.192-29.184-3.584q-11.264 0-25.088 4.096t-28.672 8.192-29.696 7.168-28.16 3.072q-12.288 0-25.6-4.096t-26.624-8.704-25.088-8.704-20.992-5.12-18.432 0.512-18.944 4.608-22.016 6.656-28.672 5.632zM254.29333334 478.208q-2.048 4.096-8.192 12.288t-17.408 14.848-28.16 7.68-41.472-8.192q-26.624-10.24-37.888-27.648t-13.312-35.84 1.024-34.816 6.144-23.552q2.048-7.168 5.632-13.312t6.656-11.264q4.096-5.12 6.144-10.24 8.192 2.048 19.456 3.072 9.216 1.024 23.04 1.536t31.232-1.536q17.408-1.024 29.184-3.584t18.944-4.608q8.192-3.072 12.288-5.12 7.168 2.048 13.312 4.096 5.12 2.048 10.752 3.072t8.704 3.072q-4.096 8.192-8.192 15.36-4.096 6.144-8.192 13.824t-8.192 13.824q-8.192 12.288-10.752 20.48t-3.584 17.408q0 9.216-1.024 25.088t-6.144 24.064zM351.57333334 360.448q9.216 2.048 20.48 4.096 9.216 2.048 20.992 2.56t24.064-0.512q11.264-1.024 23.552-2.048t21.504-2.048q11.264-1.024 21.504-2.048l0 100.352q0 6.144-3.072 17.408t-11.776 22.016-23.552 18.432-37.376 7.68q-34.816 0-52.736-10.24t-26.112-24.064-9.728-27.136-1.536-19.456q0-12.288 0.512-19.968t5.632-18.944q2.048-5.12 6.656-13.312t9.728-15.36q5.12-8.192 11.264-17.408zM610.64533334 369.664q11.264 1.024 23.04 0.512t20.992-1.536q11.264-1.024 19.456-3.072 4.096 7.168 8.192 13.312 7.168 12.288 14.336 21.504 6.144 9.216 8.192 23.552t2.048 26.624q0 6.144-2.56 18.432t-11.264 25.088-26.112 22.528-48.128 9.728q-21.504 0-36.352-7.168t-24.064-16.896-13.312-19.968-4.096-16.384l0-105.472 22.528 4.096q10.24 2.048 22.528 3.072zM918.86933334 391.168q6.144 14.336 6.656 35.84t-8.192 41.472-27.136 33.792-50.176 13.824q-23.552 0-38.4-8.704t-23.552-20.48-11.776-24.576-3.072-22.016q0-17.408-2.56-23.04t-4.608-12.8q-2.048-3.072-6.656-11.776t-9.728-16.896q-6.144-10.24-12.288-21.504 4.096-1.024 10.24-3.072l12.288-4.096 14.336-6.144q12.288 6.144 24.576 10.24 11.264 4.096 25.088 7.168t29.184 5.12q17.408 2.048 28.16 2.56t17.92-0.512q8.192-1.024 12.288-3.072 0-2.048 2.048 0t15.36 28.672zM789.84533334 557.056q6.144 2.048 11.776 1.536t9.728-0.512 9.216-1.024l0 270.336q0 37.888-12.8 56.832t-51.712 18.944l-47.104 0-82.944 0-99.328 0-100.352 0-86.016 0-52.224 0q-38.912 0-57.344-18.432t-18.432-52.224l0-153.6 0-119.808 10.24 0 11.264 0q5.12 0 10.24-1.024 5.12 0 10.752-3.584t10.752-7.68 10.24-10.24l0 90.112 0 69.632q0 32.768 1.024 46.08 0 14.336 11.776 22.016t29.184 7.68l34.816 0q29.696 0 71.68 0.512t90.624 1.024 91.648 0.512l73.728 0 40.96 0q31.744-1.024 33.792-36.864l0-196.608q6.144 3.072 12.288 7.168 5.12 3.072 11.264 5.632t11.264 3.584z" ></path></symbol><symbol id="icon-user" viewBox="0 0 1024 1024"><path d="M497.377 551.253c69.509-37.109 116.969-110.333 116.969-194.478 0-121.515-98.864-220.393-220.379-220.393s-220.379 98.864-220.379 220.393c0 84.828 48.251 158.487 118.675 195.311-153.409 44.947-267.168 186.354-269.366 353.513l40.96 0.532c2.375-180.907 151.498-328.076 332.418-328.076s330.042 147.169 332.404 328.076l40.96-0.532c-2.212-168.209-117.405-310.34-272.261-354.345zM214.548 356.775c0-98.932 80.486-179.433 179.419-179.433s179.419 80.5 179.419 179.433-80.486 179.419-179.419 179.419-179.419-80.486-179.419-179.419z" ></path><path d="M729.47 551.253c69.523-37.109 116.969-110.333 116.969-194.492 0-119.644-95.887-217.102-214.821-220.092v40.96c96.352 2.963 173.861 82.070 173.861 179.132s-77.51 176.169-173.861 179.146v42.229c179.445 1.747 326.82 148.179 329.168 327.994l40.96-0.532c-2.198-168.209-117.405-310.34-272.275-354.345z" ></path></symbol><symbol id="icon-cart" viewBox="0 0 1024 1024"><path d="M848 712H318.4c-25.6 0-46.4-19.2-48-41.6L240 201.6c-1.6-25.6-16-46.4-38.4-57.6l-48-20.8c-12.8-4.8-25.6 0-32 12.8s0 25.6 12.8 32l46.4 20.8c6.4 3.2 11.2 9.6 11.2 17.6l28.8 468.8c3.2 48 46.4 86.4 96 86.4H848c12.8 0 24-11.2 24-24s-11.2-25.6-24-25.6z" ></path><path d="M884.8 265.6c-14.4-16-35.2-25.6-57.6-25.6H337.6c-12.8 0-24 11.2-24 24s11.2 24 24 24h489.6c8 0 16 3.2 20.8 9.6 4.8 6.4 8 14.4 8 22.4l-38.4 211.2v1.6c-1.6 14.4-12.8 24-27.2 25.6l-420.8 32c-12.8 1.6-22.4 12.8-22.4 25.6 1.6 12.8 11.2 22.4 24 22.4h1.6l419.2-32c36.8-3.2 67.2-30.4 70.4-67.2l38.4-212.8v-1.6c4.8-20.8-1.6-43.2-16-59.2z" ></path><path d="M305.6 856m-56 0a56 56 0 1 0 112 0 56 56 0 1 0-112 0Z" ></path><path d="M753.6 856m-56 0a56 56 0 1 0 112 0 56 56 0 1 0-112 0Z" ></path></symbol><symbol id="icon-category" viewBox="0 0 1024 1024"><path d="M933.875 512c0-10.546875-5.2734375-21.09375-15.8203125-26.3671875l-94.921875-52.734375 94.921875-52.734375c10.546875 0 15.8203125-10.546875 15.8203125-26.3671875 0-10.546875-5.2734375-21.09375-15.8203125-26.3671875L527.8203125 121.765625c-10.546875-5.2734375-21.09375-5.2734375-26.3671875 0L105.9453125 327.4296875c-10.546875 5.2734375-15.8203125 15.8203125-15.8203125 26.3671875s5.2734375 21.09375 15.8203125 26.3671875l94.921875 52.734375-94.921875 52.734375c-10.546875 5.2734375-15.8203125 15.8203125-15.8203125 26.3671875s5.2734375 21.09375 15.8203125 26.3671875l94.921875 52.734375-94.921875 52.734375c-10.546875 0-15.8203125 10.546875-15.8203125 26.3671875 0 10.546875 5.2734375 21.09375 15.8203125 26.3671875l390.234375 205.6640625c5.2734375 0 10.546875 5.2734375 15.8203125 5.2734375 5.2734375 0 10.546875 0 15.8203125-5.2734375l390.234375-205.6640625c10.546875-5.2734375 15.8203125-15.8203125 15.8203125-26.3671875s-5.2734375-21.09375-15.8203125-26.3671875l-94.921875-52.734375 94.921875-52.734375c10.546875-5.2734375 15.8203125-15.8203125 15.8203125-26.3671875zM185.046875 353.796875L512 185.046875 838.953125 353.796875 512 527.8203125 185.046875 353.796875z m653.90625 316.40625L512 838.953125 185.046875 670.203125l79.1015625-42.1875 232.03125 121.2890625c5.2734375 0 10.546875 5.2734375 15.8203125 5.2734375 5.2734375 0 10.546875 0 15.8203125-5.2734375l232.03125-121.2890625 79.1015625 42.1875z m-326.953125 15.8203125L185.046875 512l79.1015625-42.1875 232.03125 121.2890625c5.2734375 0 10.546875 5.2734375 15.8203125 5.2734375 5.2734375 0 10.546875 0 15.8203125-5.2734375l232.03125-121.2890625 79.1015625 42.1875L512 686.0234375z" ></path></symbol><symbol id="icon-home-copy" viewBox="0 0 1024 1024"><path d="M216.40533334 308.224q-34.816 3.072-48.128 2.048-9.216 0-15.36-2.048 19.456-34.816 35.84-63.488 14.336-24.576 27.136-48.128t17.92-31.744q9.216-16.384 20.992-24.064t33.28-7.68l39.936 0 79.872 0 100.352 0 100.352 0 80.896 0 41.984 0q12.288 0 22.528 0.512t18.432 4.096 15.872 11.264 15.872 22.016l22.528 39.936 28.672 49.152q16.384 28.672 34.816 60.416-8.192 1.024-18.432 1.024-18.432 0-46.08-6.144-20.48-4.096-41.472-12.288t-35.328-8.192q-11.264 0-23.04 4.096t-25.6 8.704-29.184 8.192-31.744 3.584-32.768-4.096-32.256-8.704-30.72-8.192-29.184-3.584q-11.264 0-25.088 4.096t-28.672 8.192-29.696 7.168-28.16 3.072q-12.288 0-25.6-4.096t-26.624-8.704-25.088-8.704-20.992-5.12-18.432 0.512-18.944 4.608-22.016 6.656-28.672 5.632zM254.29333334 478.208q-2.048 4.096-8.192 12.288t-17.408 14.848-28.16 7.68-41.472-8.192q-26.624-10.24-37.888-27.648t-13.312-35.84 1.024-34.816 6.144-23.552q2.048-7.168 5.632-13.312t6.656-11.264q4.096-5.12 6.144-10.24 8.192 2.048 19.456 3.072 9.216 1.024 23.04 1.536t31.232-1.536q17.408-1.024 29.184-3.584t18.944-4.608q8.192-3.072 12.288-5.12 7.168 2.048 13.312 4.096 5.12 2.048 10.752 3.072t8.704 3.072q-4.096 8.192-8.192 15.36-4.096 6.144-8.192 13.824t-8.192 13.824q-8.192 12.288-10.752 20.48t-3.584 17.408q0 9.216-1.024 25.088t-6.144 24.064zM351.57333334 360.448q9.216 2.048 20.48 4.096 9.216 2.048 20.992 2.56t24.064-0.512q11.264-1.024 23.552-2.048t21.504-2.048q11.264-1.024 21.504-2.048l0 100.352q0 6.144-3.072 17.408t-11.776 22.016-23.552 18.432-37.376 7.68q-34.816 0-52.736-10.24t-26.112-24.064-9.728-27.136-1.536-19.456q0-12.288 0.512-19.968t5.632-18.944q2.048-5.12 6.656-13.312t9.728-15.36q5.12-8.192 11.264-17.408zM610.64533334 369.664q11.264 1.024 23.04 0.512t20.992-1.536q11.264-1.024 19.456-3.072 4.096 7.168 8.192 13.312 7.168 12.288 14.336 21.504 6.144 9.216 8.192 23.552t2.048 26.624q0 6.144-2.56 18.432t-11.264 25.088-26.112 22.528-48.128 9.728q-21.504 0-36.352-7.168t-24.064-16.896-13.312-19.968-4.096-16.384l0-105.472 22.528 4.096q10.24 2.048 22.528 3.072zM918.86933334 391.168q6.144 14.336 6.656 35.84t-8.192 41.472-27.136 33.792-50.176 13.824q-23.552 0-38.4-8.704t-23.552-20.48-11.776-24.576-3.072-22.016q0-17.408-2.56-23.04t-4.608-12.8q-2.048-3.072-6.656-11.776t-9.728-16.896q-6.144-10.24-12.288-21.504 4.096-1.024 10.24-3.072l12.288-4.096 14.336-6.144q12.288 6.144 24.576 10.24 11.264 4.096 25.088 7.168t29.184 5.12q17.408 2.048 28.16 2.56t17.92-0.512q8.192-1.024 12.288-3.072 0-2.048 2.048 0t15.36 28.672zM789.84533334 557.056q6.144 2.048 11.776 1.536t9.728-0.512 9.216-1.024l0 270.336q0 37.888-12.8 56.832t-51.712 18.944l-47.104 0-82.944 0-99.328 0-100.352 0-86.016 0-52.224 0q-38.912 0-57.344-18.432t-18.432-52.224l0-153.6 0-119.808 10.24 0 11.264 0q5.12 0 10.24-1.024 5.12 0 10.752-3.584t10.752-7.68 10.24-10.24l0 90.112 0 69.632q0 32.768 1.024 46.08 0 14.336 11.776 22.016t29.184 7.68l34.816 0q29.696 0 71.68 0.512t90.624 1.024 91.648 0.512l73.728 0 40.96 0q31.744-1.024 33.792-36.864l0-196.608q6.144 3.072 12.288 7.168 5.12 3.072 11.264 5.632t11.264 3.584z" ></path></symbol><symbol id="icon-user-copy" viewBox="0 0 1024 1024"><path d="M497.377 551.253c69.509-37.109 116.969-110.333 116.969-194.478 0-121.515-98.864-220.393-220.379-220.393s-220.379 98.864-220.379 220.393c0 84.828 48.251 158.487 118.675 195.311-153.409 44.947-267.168 186.354-269.366 353.513l40.96 0.532c2.375-180.907 151.498-328.076 332.418-328.076s330.042 147.169 332.404 328.076l40.96-0.532c-2.212-168.209-117.405-310.34-272.261-354.345zM214.548 356.775c0-98.932 80.486-179.433 179.419-179.433s179.419 80.5 179.419 179.433-80.486 179.419-179.419 179.419-179.419-80.486-179.419-179.419z" ></path><path d="M729.47 551.253c69.523-37.109 116.969-110.333 116.969-194.492 0-119.644-95.887-217.102-214.821-220.092v40.96c96.352 2.963 173.861 82.070 173.861 179.132s-77.51 176.169-173.861 179.146v42.229c179.445 1.747 326.82 148.179 329.168 327.994l40.96-0.532c-2.198-168.209-117.405-310.34-272.275-354.345z" ></path></symbol><symbol id="icon-cart-copy" viewBox="0 0 1024 1024"><path d="M848 712H318.4c-25.6 0-46.4-19.2-48-41.6L240 201.6c-1.6-25.6-16-46.4-38.4-57.6l-48-20.8c-12.8-4.8-25.6 0-32 12.8s0 25.6 12.8 32l46.4 20.8c6.4 3.2 11.2 9.6 11.2 17.6l28.8 468.8c3.2 48 46.4 86.4 96 86.4H848c12.8 0 24-11.2 24-24s-11.2-25.6-24-25.6z" ></path><path d="M884.8 265.6c-14.4-16-35.2-25.6-57.6-25.6H337.6c-12.8 0-24 11.2-24 24s11.2 24 24 24h489.6c8 0 16 3.2 20.8 9.6 4.8 6.4 8 14.4 8 22.4l-38.4 211.2v1.6c-1.6 14.4-12.8 24-27.2 25.6l-420.8 32c-12.8 1.6-22.4 12.8-22.4 25.6 1.6 12.8 11.2 22.4 24 22.4h1.6l419.2-32c36.8-3.2 67.2-30.4 70.4-67.2l38.4-212.8v-1.6c4.8-20.8-1.6-43.2-16-59.2z" ></path><path d="M305.6 856m-56 0a56 56 0 1 0 112 0 56 56 0 1 0-112 0Z" ></path><path d="M753.6 856m-56 0a56 56 0 1 0 112 0 56 56 0 1 0-112 0Z" ></path></symbol><symbol id="icon-category-copy" viewBox="0 0 1024 1024"><path d="M933.875 512c0-10.546875-5.2734375-21.09375-15.8203125-26.3671875l-94.921875-52.734375 94.921875-52.734375c10.546875 0 15.8203125-10.546875 15.8203125-26.3671875 0-10.546875-5.2734375-21.09375-15.8203125-26.3671875L527.8203125 121.765625c-10.546875-5.2734375-21.09375-5.2734375-26.3671875 0L105.9453125 327.4296875c-10.546875 5.2734375-15.8203125 15.8203125-15.8203125 26.3671875s5.2734375 21.09375 15.8203125 26.3671875l94.921875 52.734375-94.921875 52.734375c-10.546875 5.2734375-15.8203125 15.8203125-15.8203125 26.3671875s5.2734375 21.09375 15.8203125 26.3671875l94.921875 52.734375-94.921875 52.734375c-10.546875 0-15.8203125 10.546875-15.8203125 26.3671875 0 10.546875 5.2734375 21.09375 15.8203125 26.3671875l390.234375 205.6640625c5.2734375 0 10.546875 5.2734375 15.8203125 5.2734375 5.2734375 0 10.546875 0 15.8203125-5.2734375l390.234375-205.6640625c10.546875-5.2734375 15.8203125-15.8203125 15.8203125-26.3671875s-5.2734375-21.09375-15.8203125-26.3671875l-94.921875-52.734375 94.921875-52.734375c10.546875-5.2734375 15.8203125-15.8203125 15.8203125-26.3671875zM185.046875 353.796875L512 185.046875 838.953125 353.796875 512 527.8203125 185.046875 353.796875z m653.90625 316.40625L512 838.953125 185.046875 670.203125l79.1015625-42.1875 232.03125 121.2890625c5.2734375 0 10.546875 5.2734375 15.8203125 5.2734375 5.2734375 0 10.546875 0 15.8203125-5.2734375l232.03125-121.2890625 79.1015625 42.1875z m-326.953125 15.8203125L185.046875 512l79.1015625-42.1875 232.03125 121.2890625c5.2734375 0 10.546875 5.2734375 15.8203125 5.2734375 5.2734375 0 10.546875 0 15.8203125-5.2734375l232.03125-121.2890625 79.1015625 42.1875L512 686.0234375z" ></path></symbol></svg>'), (function (e) { var t = (t = document.getElementsByTagName('script'))[t.length - 1], l = t.getAttribute('data-injectcss'), t = t.getAttribute('data-disable-injectsvg') if (!t) { var c, a, o, i, q, n = function (t, l) { l.parentNode.insertBefore(t, l) } if (l && !e.__iconfont__svg__cssinject__) { e.__iconfont__svg__cssinject__ = !0 try { document.write( '<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>' ) } catch (t) { console && console.log(t) } } ;(c = function () { var t, l = document.createElement('div') ;(l.innerHTML = e._iconfont_svg_string_4175143), (l = l.getElementsByTagName('svg')[0]) && (l.setAttribute('aria-hidden', 'true'), (l.style.position = 'absolute'), (l.style.width = 0), (l.style.height = 0), (l.style.overflow = 'hidden'), (l = l), (t = document.body).firstChild ? n(l, t.firstChild) : t.appendChild(l)) }), document.addEventListener ? ~['complete', 'loaded', 'interactive'].indexOf(document.readyState) ? setTimeout(c, 0) : ((a = function () { document.removeEventListener('DOMContentLoaded', a, !1), c() }), document.addEventListener('DOMContentLoaded', a, !1)) : document.attachEvent && ((o = c), (i = e.document), (q = !1), d(), (i.onreadystatechange = function () { 'complete' == i.readyState && ((i.onreadystatechange = null), s()) })) } function s() { q || ((q = !0), o()) } function d() { try { i.documentElement.doScroll('left') } catch (t) { return void setTimeout(d, 50) } s() } })(window)
-
-
src/main.js中导入图标所在的js文件
-
src/main.js
import './assets/iconfont'
-
-
创建一个svg的通用样式组件。
-
src/components/SvgIcon.vue
<template> <svg :class="svgClass" aria-hidden="true"> <use :xlink:href="iconClass"></use> </svg> </template> <script setup> // icon-class 是决定用户采用的是哪一张图片 // custorm-class 给icon设置class 可以采用这个字段 const props = defineProps(['icon-class', 'custorm-class']) const iconClass = computed(() => `#icon-${props.iconClass}`) const svgClass = computed(() => props['custorm-class'] ? `icon ${props['custorm-class']}` : 'icon' ) </script> <style scoped> .icon { width: 1em; height: 1em; vertical-align: -0.15em; fill: currentColor; overflow: hidden; } </style>
-
-
在需要用到的地方里,使用该svg通用样式组件。
-
src/App.vue 注:SvgIcon组件由于在src/components中,之前已经把其配置为全局组件了。
<template> <van-tabbar route> <van-tabbar-item to="/home"> <template #icon> <SvgIcon icon-class="home"></SvgIcon> </template> 首页 </van-tabbar-item> <van-tabbar-item to="/category"> <template #icon> <SvgIcon icon-class="category"></SvgIcon> </template> 分类 </van-tabbar-item> <van-tabbar-item to="/cart"> <template #icon> <SvgIcon icon-class="cart"></SvgIcon> </template> 购物车 </van-tabbar-item> <van-tabbar-item to="/user"> <template #icon> <SvgIcon icon-class="user"></SvgIcon> </template> 我的 </van-tabbar-item> </van-tabbar> </template>
-
修改UI框架的主题色
- src/assets/theme.css
:root:root {
--van-primary-color: #1baeae;
}
- src/main.js
import './assets/theme.css'
- src/App.vue
<template>
<van-tabbar route>
<van-tabbar-item to="/home">
<template #icon>
<SvgIcon icon-class="home"></SvgIcon>
</template>
首页
</van-tabbar-item>
<van-tabbar-item to="/category">
<template #icon>
<SvgIcon icon-class="category"></SvgIcon>
</template>
分类
</van-tabbar-item>
<van-tabbar-item to="/cart">
<template #icon>
<SvgIcon icon-class="cart"></SvgIcon>
</template>
购物车
</van-tabbar-item>
<van-tabbar-item to="/user">
<template #icon>
<SvgIcon icon-class="user"></SvgIcon>
</template>
我的
</van-tabbar-item>
</van-tabbar>
</template>
使用less修改UI框架的主题色
pnpm install less
- src/assets/var.less
@theme: #1baeae;
- vite.config.js
export default defineConfig({
css: {
preprocessorOptions: {
less: {
additionalData: '@import "/src/assets/var.less";'
}
}
}
})
- src/App.vue
<template>
<div class="content">content</div>
</template>
<style scoped lang="less">
.content {
color: @theme;
}
</style>
设置导航不遮住内容
- src/App.vue
<script setup>
const isVisible = ref(true)
</script>
<template>
<!-- wrapper 用来控制是否有底部的padding -->
<div id="wrapper" :class="{ hasBottom: isVisible }">
<router-view></router-view>
<!-- 让navbar组件告诉我是否需要有padding -->
<NavBar v-model:visible="isVisible"></NavBar>
</div>
</template>
<style scoped lang="less">
.hasBottom {
padding-bottom: 50px;
}
</style>
- src/components/NavBar.vue
<template>
<van-tabbar route v-if="props.visible">
<van-tabbar-item to="/home">
<template #icon>
<SvgIcon icon-class="home"></SvgIcon>
</template>
首页</van-tabbar-item
>
<van-tabbar-item to="/category">
<template #icon>
<SvgIcon icon-class="category"></SvgIcon>
</template>
分类</van-tabbar-item
>
<van-tabbar-item to="/cart">
<template #icon>
<SvgIcon icon-class="cart"></SvgIcon>
</template>
购物车</van-tabbar-item
>
<van-tabbar-item to="/user">
<template #icon>
<SvgIcon icon-class="user"></SvgIcon>
</template>
我的</van-tabbar-item
>
</van-tabbar>
</template>
<script setup>
const route = useRoute()
// 父组件告诉我要不要渲染,我自己判断如果需要渲染,告诉父亲
const props = defineProps(['visible'])
const emit = defineEmits(['update:visible'])
watchEffect(() => {
// 根据路由判断是否要渲染
const paths = ['/home', '/category', '/cart', '/user']
// 如果访问的是这几个页面
emit('update:visible', paths.includes(route.path))
// 通知父组件我的最新状态是多少
})
</script>
首页上方假搜索
- src/views/Home.vue
<template>
<van-sticky>
<div class="home-header">
<div class="menu">
<router-link to="/category">
<van-icon name="wap-nav" to="/category" />
</router-link>
</div>
<div class="search">
<span>新峰商城</span>
<van-icon name="search" />
<router-link to="/search">山河无恙,人间皆安</router-link>
</div>
<div class="login">
<router-link to="/login">登录</router-link>
</div>
</div>
</van-sticky>
</template>
<style scoped lang="less">
.home-header {
padding: 0 20px;
height: 50px;
display: flex;
justify-content: space-between;
align-items: center;
a {
color: #444;
}
}
.search {
width: 255px;
}
.van-icon {
font-size: 20px;
}
.search {
span {
color: @theme;
font-size: 22px;
padding-right: 20px;
}
line-height: 50px;
display: flex;
align-items: center;
}
</style>
请求封装
pnpm install axios
- src/utils/http.js
import axios from 'axios'
// 请求的时候可以做什么事? 1)拦截 携带token, 对响应状态码处理, 2)增加loading (增添一个队列) 3)接口可以保存取消的操作
class Http {
constructor() {
// 根据环境变量设置请求的路径
this.baseURL = import.meta.env.DEV ? 'http://backend-api-01.newbee.ltd/api/v1' : '/'
this.timeout = 5000
}
setInterceptor(instance) {
instance.interceptors.request.use(
(config) => {
// 携带token来做处理
return config
},
(err) => {
return Promise.reject(err)
}
)
instance.interceptors.response.use(
(res) => {
return res.data
},
(err) => {
return Promise.reject(err)
}
)
}
request(options) {
// 请求会实现拦截器
const instance = axios.create() // 1.每次请求要创建一个新的实例
this.setInterceptor(instance) // 2.设置拦截器
// 发送请求参数
return instance({
...options,
baseURL: this.baseURL,
timeout: this.timeout
})
}
get(url, data) {
return this.request({
method: 'get',
url,
params: data
})
}
post(url, data) {
return this.request({
method: 'post',
url,
data
})
}
}
export default new Http()
- src/views/Home.vue
<script setup>
import http from '@/utils/http.js'
http.get('/index-infos').then((res) => {
console.log(res.data)
})
</script>
API的统一封装处理
- src/api/index.js
import http from '@/utils/http.js'
const API_LIST = {
queryIndexInfos: '/index-infos' // 首页的获取数据接口,都在这里
}
// 首页数据的获取,直接通过 api 这个文件来操作
export function queryIndexInfos() {
return http.get(API_LIST.queryIndexInfos)
}
- src/views/Home.vue
<script setup>
import { queryIndexInfos } from '../api/index'
const carousels = ref([])
onMounted(async () => {
let { data } = await queryIndexInfos()
carousels.value = data.carousels // 轮播图的数据获取成功
})
</script>
<template>
<van-sticky z-index="100">
<div class="home-header">
<div class="menu">
<router-link to="/category">
<van-icon name="wap-nav" />
</router-link>
</div>
<div class="search">
<span>新峰商城</span>
<van-icon name="search" />
<router-link to="/search">山河无恙,人间皆安</router-link>
</div>
<div class="login">
<router-link to="/login">登录</router-link>
</div>
</div>
</van-sticky>
<van-swipe class="swipe" :autoplay="3000" indicator-color="@theme">
<template v-for="(item, index) of carousels" :key="index">
<van-swipe-item>
<img :src="item.carouselUrl" />
</van-swipe-item>
</template>
</van-swipe>
</template>
<style scoped lang="less">
.home-header {
padding: 0 20px;
height: 50px;
display: flex;
justify-content: space-between;
align-items: center;
a {
color: #444;
}
}
.search {
width: 255px;
}
.van-icon {
font-size: 20px;
}
.search {
span {
color: @theme;
font-size: 22px;
padding-right: 20px;
font-weight: bold;
}
line-height: 50px;
display: flex;
align-items: center;
}
.swipe {
height: 172px;
img {
width: 100%;
height: 100%;
}
}
</style>
吸项效果
- 用一个例子包起
<van-sticky>
,之后例子设置定位。
- src/views/Home.vue
<template>
<div style="position: absolute; width: 100%; z-index: 1">
<van-sticky z-index="100" @scroll="handleScroll">
<div class="home-header" :class="{ active: isActive }">
<div class="menu">
<router-link to="/category">
<van-icon name="wap-nav" />
</router-link>
</div>
<div class="search">
<span>新峰商城</span>
<van-icon name="search" />
<router-link to="/search">山河无恙,人间皆安</router-link>
</div>
<div class="login">
<router-link to="/login">登录</router-link>
</div>
</div>
</van-sticky>
</div>
</template>
静态数据渲染
- src/assets/category.js
export default [
{
name: '新蜂超市',
imgUrl: 'https://s.yezgea02.com/1604041127880/%E8%B6%85%E5%B8%82%402x.png',
categoryId: 100001
},
{
name: '新蜂服饰',
imgUrl: 'https://s.yezgea02.com/1604041127880/%E6%9C%8D%E9%A5%B0%402x.png',
categoryId: 100003
},
{
name: '全球购',
imgUrl: 'https://s.yezgea02.com/1604041127880/%E5%85%A8%E7%90%83%E8%B4%AD%402x.png',
categoryId: 100002
},
{
name: '新蜂生鲜',
imgUrl: 'https://s.yezgea02.com/1604041127880/%E7%94%9F%E9%B2%9C%402x.png',
categoryId: 100004
},
{
name: '新蜂到家',
imgUrl: 'https://s.yezgea02.com/1604041127880/%E5%88%B0%E5%AE%B6%402x.png',
categoryId: 100005
},
{
name: '充值缴费',
imgUrl: 'https://s.yezgea02.com/1604041127880/%E5%85%85%E5%80%BC%402x.png',
categoryId: 100006
},
{
name: '9.9元拼',
imgUrl: 'https://s.yezgea02.com/1604041127880/9.9%402x.png',
categoryId: 100007
},
{
name: '领劵',
imgUrl: 'https://s.yezgea02.com/1604041127880/%E9%A2%86%E5%88%B8%402x.png',
categoryId: 100008
},
{
name: '省钱',
imgUrl: 'https://s.yezgea02.com/1604041127880/%E7%9C%81%E9%92%B1%402x.png',
categoryId: 100009
},
{
name: '全部',
imgUrl: 'https://s.yezgea02.com/1604041127880/%E5%85%A8%E9%83%A8%402x.png',
categoryId: 100010
}
]
- src/views/Home.vue
<script setup>
import menuList from '@/assets/category.js'
</script>
<template>
<van-grid :column-num="5" :border="false">
<van-grid-item
v-for="menu in menuList"
:key="menu.name"
:icon="menu.imgUrl"
:text="menu.name"
/>
</van-grid>
</template>
滚动后设置背景
- src/views/Home.vue
<script setup>
const isActive = ref(false)
function handleScroll({ scrollTop }) {
isActive.value = scrollTop > 172
}
</script>
<template>
<van-sticky z-index="100" @scroll="handleScroll">
<div class="home-header" :class="{ active: isActive }">
<div class="menu">
<router-link to="/category">
<van-icon name="wap-nav" />
</router-link>
</div>
<div class="search">
<span>新峰商城</span>
<van-icon name="search" />
<router-link to="/search">山河无恙,人间皆安</router-link>
</div>
<div class="login">
<router-link to="/login">登录</router-link>
</div>
</div>
</van-sticky>
</template>
<style scoped lang="less">
.home-header {
padding: 0 20px;
display: flex;
background: rgba(255, 255, 255, 0.7);
justify-content: space-between;
align-items: center;
a {
color: #444;
}
&.active {
background: #1baeae;
span {
color: #fff;
}
a {
color: #fff;
}
}
}
</style>
点击出现弹框
- src/views/Home.vue
<script setup>
const menuList = [
{
name: '新蜂超市',
imgUrl: 'https://s.yezgea02.com/1604041127880/%E8%B6%85%E5%B8%82%402x.png',
categoryId: 100001
},
{
name: '新蜂服饰',
imgUrl: 'https://s.yezgea02.com/1604041127880/%E6%9C%8D%E9%A5%B0%402x.png',
categoryId: 100003
},
{
name: '全球购',
imgUrl: 'https://s.yezgea02.com/1604041127880/%E5%85%A8%E7%90%83%E8%B4%AD%402x.png',
categoryId: 100002
},
{
name: '新蜂生鲜',
imgUrl: 'https://s.yezgea02.com/1604041127880/%E7%94%9F%E9%B2%9C%402x.png',
categoryId: 100004
},
{
name: '新蜂到家',
imgUrl: 'https://s.yezgea02.com/1604041127880/%E5%88%B0%E5%AE%B6%402x.png',
categoryId: 100005
},
{
name: '充值缴费',
imgUrl: 'https://s.yezgea02.com/1604041127880/%E5%85%85%E5%80%BC%402x.png',
categoryId: 100006
},
{
name: '9.9元拼',
imgUrl: 'https://s.yezgea02.com/1604041127880/9.9%402x.png',
categoryId: 100007
},
{
name: '领劵',
imgUrl: 'https://s.yezgea02.com/1604041127880/%E9%A2%86%E5%88%B8%402x.png',
categoryId: 100008
},
{
name: '省钱',
imgUrl: 'https://s.yezgea02.com/1604041127880/%E7%9C%81%E9%92%B1%402x.png',
categoryId: 100009
},
{
name: '全部',
imgUrl: 'https://s.yezgea02.com/1604041127880/%E5%85%A8%E9%83%A8%402x.png',
categoryId: 100010
}
]
</script>
<template>
<van-grid :column-num="5" :border="false">
<van-grid-item
v-for="menu in menuList"
:key="menu.name"
:icon="menu.imgUrl"
:text="menu.name"
@click="$showToast('敬请期待')"
/>
</van-grid>
</template>
<style scoped lang="less">
.home-header {
padding: 0 20px;
display: flex;
background: rgba(255, 255, 255, 0.7);
justify-content: space-between;
align-items: center;
a {
color: #444;
}
&.active {
background: #1baeae;
span {
color: #fff;
}
a {
color: #fff;
}
}
}
</style>
骨架屏
- src/views/Home.vue
<script setup>
const hotGoodses = ref([])
const newGoodses = ref([])
const recommendGoodses = ref([])
const loading = ref(true)
onMounted(async () => {
let { data } = await queryIndexInfos()
carousels.value = data.carousels // 轮播图的数据获取成功
hotGoodses.value = data.hotGoodses
newGoodses.value = data.newGoodses
recommendGoodses.value = data.recommendGoodses
loading.value = false
})
</script>
<template>
<van-skeleton :row="5" :loading="loading">
<div class="title">新品上线</div>
<van-grid :column-num="2" :border="false">
<van-grid-item v-for="goods in hotGoodses" :key="goods.goodsId">
<van-image :src="goods.goodsCoverImg" lazy-load />
<div class="desc">
{{ goods.goodsIntro }}
</div>
<div class="price">
{{ goods.sellingPrice }}
</div>
</van-grid-item>
</van-grid>
</van-skeleton>
</template>
图片设置懒加载
- 设置 lazy-load 属性来开启图片懒加载,需要搭配 Lazyload 组件使用。
<template>
<van-image
width="100"
height="100"
lazy-load
src="https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg"
/>
</template>
- src/main.js
import { createApp } from 'vue';
import { Lazyload } from 'vant';
const app = createApp();
app.use(Lazyload);
// 注册时可以配置额外的选项
app.use(Lazyload, {
lazyComponent: true,
});
抽离静态组件
- src/components/HomeGoodsItem.vue
<script setup>
defineProps(['title', 'goodsList'])
</script>
<template>
<div class="title">{{ title }}</div>
<van-grid :column-num="2" :border="false">
<van-grid-item v-for="goods in goodsList" :key="goods.goodsId">
<van-image :src="goods.goodsCoverImg" lazy-load />
<div class="desc">
{{ goods.goodsIntro }}
</div>
<div class="price">
{{ goods.sellingPrice }}
</div>
</van-grid-item>
</van-grid>
</template>
<style scoped lang="less">
.title {
color: @theme;
font-size: 20px;
text-align: center;
font-weight: bold;
padding: 10px 0;
}
.van-image {
:deep(.van-image__img) {
width: 120px;
height: 140px;
}
}
.desc {
font-size: 14px;
color: #555;
}
.price {
color: @theme;
font-size: 16px;
}
</style>
- src/views/Home.vue
<script setup>
import { queryIndexInfos } from '../api/index'
const hotGoodses = ref([])
const newGoodses = ref([])
const recommendGoodses = ref([])
const loading = ref(true)
onMounted(async () => {
let { data } = await queryIndexInfos()
hotGoodses.value = data.hotGoodses
newGoodses.value = data.newGoodses
recommendGoodses.value = data.recommendGoodses
loading.value = false
})
</script>
<template>
<div style="position: absolute; width: 100%; z-index: 100">
<van-skeleton :row="5" :loading="loading">
<HomeGoodsItem title="新品上线" :goods-list="hotGoodses"></HomeGoodsItem>
<HomeGoodsItem title="热门商品" :goods-list="newGoodses"></HomeGoodsItem>
<HomeGoodsItem title="最新推荐" :goods-list="recommendGoodses"></HomeGoodsItem>
</van-skeleton>
</div>
</template>
<style scoped lang="less">
</style>
处理图片-公共方法
- src/utils/index.js
export function processURL(imgUrl) {
// 如果你是以https开头的,直接采用即可
// 否则则增加访问地址
if (!/^https?:\/\//.test(imgUrl)) {
return `http://backend-api-01.newbee.ltd${imgUrl}`
}
return imgUrl
}
export function addPrefix(money) {
// 如果你是以https开头的,直接采用即可
// 否则则增加访问地址
return '¥' + money
}
- src/components/HomeGoodsItem.vue
<script setup>
import { processURL, addPrefix } from '../utils'
defineProps(['title', 'goodsList'])
</script>
<template>
<div class="title">{{ title }}</div>
<van-grid :column-num="2" :border="false">
<van-grid-item v-for="goods in goodsList" :key="goods.goodsId">
<van-image :src="processURL(goods.goodsCoverImg)" lazy-load />
<div class="desc">
{{ goods.goodsIntro }}
</div>
<div class="price">
{{ addPrefix(goods.sellingPrice) }}
</div>
</van-grid-item>
</van-grid>
</template>
<style scoped lang="less">
.title {
color: @theme;
font-size: 20px;
text-align: center;
font-weight: bold;
padding: 10px 0;
}
.van-image {
:deep(.van-image__img) {
width: 120px;
height: 140px;
}
}
.desc {
font-size: 14px;
color: #555;
}
.price {
color: @theme;
font-size: 16px;
}
</style>
- src/views/Home.vue
<script setup>
import { queryIndexInfos } from '../api/index'
const hotGoodses = ref([])
const newGoodses = ref([])
const recommendGoodses = ref([])
const loading = ref(true)
onMounted(async () => {
let { data } = await queryIndexInfos()
hotGoodses.value = data.hotGoodses
newGoodses.value = data.newGoodses
recommendGoodses.value = data.recommendGoodses
loading.value = false
})
</script>
<template>
<div style="position: absolute; width: 100%; z-index: 100">
<van-skeleton :row="5" :loading="loading">
<HomeGoodsItem title="新品上线" :goods-list="hotGoodses"></HomeGoodsItem>
<HomeGoodsItem title="热门商品" :goods-list="newGoodses"></HomeGoodsItem>
<HomeGoodsItem title="最新推荐" :goods-list="recommendGoodses"></HomeGoodsItem>
</van-skeleton>
</div>
</template>
<style scoped lang="less">
</style>
二次封装vant组件
- src/components/TopBar.vue
<template>
<van-nav-bar left-arrow>
<template #title>
<slot name="title">
{{ title || $route.meta.title }}
</slot>
</template>
<template #right>
<slot name="right"></slot>
</template>
</van-nav-bar>
</template>
<script setup>
defineProps(['title'])
</script>
- src/views/Category.vue
<template>
<TopBar>
<template #title>
<van-search @click="$router.push('/search')"></van-search>
</template>
</TopBar>
</template>
<style scoped lang="less">
.van-search {
:deep(.van-search__content) {
border-radius: 20px;
}
}
</style>