20230721----重返学习-vue3实战项目初步

day-117-one-hundred-and-seventeen-20230721-vue3实战项目初步

vue3实战项目初步

创建vue3项目

清空多余的文件

  • 移除 assets 资源文件
  • 移除 components 目录下的所有组件
  • 删除 router 中的路由配置,页面的配置
  • 移除 stores 中的状态管理
  • 移除 views 下的所有页面组件
  • app.vue 中删除 没用的信息
  • main.js 中移除导入的 css 文件

安装项目需要的vscode插件

  • ESLint插件 保证代码格式是正确的,符合规范

支持pxtoRem

  1. 根据当前的屏幕大小/10来计算根节点的font-size

    pnpm i lib-flexible # 引入样式
    
    • src/main.js

      import 'lib-flexible'
      
  2. 把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不好区分
  3. 报红,配置.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>
      

组件按需导入

  1. 安装依赖

    pnpm i unplugin-vue-components #组件按需导入
    
    pnpm i vant #UI框架主体
    
  2. 配置插件需要的配置项

    • 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>
        
  3. 配置全局属性或全局方法

    • 有些组件不能直接按需导入,需要配置到全局属性或全局指令或全局方法上

      • 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;
  1. 每次提交前执行npm run lint;

  2. 使用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混用导致的问题)

  • 对提交的注释格式要求更严格;
  1. 安装依赖

    pnpm install @commitlint/cli @commitlint/config-conventional -D
    
  2. 添加钩子命令

    npx husky add .husky/commit-msg 'npx --no-install commitlint --edit `echo "\$1"`'
    
  3. 设置插件

    • commitlint.config.cjs
    module.exports = {
      extends: ["@commitlint/config-conventional"],
    };
    
  4. 每次提交时,要加前缀,写的注释有要求。前缀样式如下

    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()]
    }),
  ],
})

字体库

  1. 选择图标,选择Symbol,得到iconfont.js。

  2. 去除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)
      
      
  3. src/main.js中导入图标所在的js文件

    • src/main.js

      import './assets/iconfont'
      
  4. 创建一个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>
      
  5. 在需要用到的地方里,使用该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框架的主题色

  1. 常见问题
  2. ConfigProvider 全局配置
  • 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>

吸项效果

  1. 用一个例子包起<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>

图片设置懒加载

  1. image
  2. lazyload
  • 设置 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>

配置@符在vue文件可以在vscode识别以便可以点击进对应的代码中

进阶参考

  1. 对应的项目-老师的
  2. 对应的项目-原版开源的
  3. 对应的项目-原版演示页面
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值