vue3+vant移动端demo

2,894 阅读3分钟

创建工程

  1. vue create vue3_h5
  2. 选择 Manually select features
  3. css预处理建议选择less,因为ui插件vant是用的less,后面定制主题不需要重复装
  4. 进入文件夹yarn serve 运行项目

安装移动端尺寸适配插件

  1. 安装yarn add postcss-px-to-viewport --save-dev
  2. 配置
根目录创建postcss.config.js并在里面添加
module.exports ={
  plugins:{
    autoprefixer:{},
    "postcss-px-to-viewport":{
      viewportWidth:375, //视窗的宽度,对应的是我们设计稿的宽度
      viewportHeight:667,//视窗的高度,对应的是我们设计稿的高度(也可以不配置)
      unitPrecision:5, //指定'px'转换为视窗单位值的小数位数(很多时候无法整除)
      viewportUnit:'vw',//指定需要转换成的视窗单位,建议使用vw
      minPixelValue:1,//小于或者等于'1px'不转换为视窗单位
      mediaQuery:false,//允许在媒体查询中转换'px'
      
      selectorBlackList:['tabBar','tabBaritem'],//指定不需要转换的类,或者在不需要转换的,写的时候后面多跟一个类名
      exclude:[/tabbar/]
    }
  }
}

安装vue3版本的vant库

  1. 安装 yarn add vant@next -S
// 自动按需引入组件 (推荐) yarn add babel-plugin-import -D
// 对于使用 babel7 的用户,可以在 babel.config.js 中配置
module.exports = {
  plugins: [
    ['import', {
      libraryName: 'vant',
      libraryDirectory: 'es',
      style: true
    }, 'vant']
  ]
};
  1. 单个页面引用
import { NavBar } from 'vant'
components: { [NavBar.name]: NavBar },
  1. main.js全局引入
import { Icon } from 'vant'
createApp(App).use(Icon)

定制vant主题

  1. 按需引入样式
在 babel.config.js 中配置按需引入样式源文件,注意 babel6 不支持按需引入样式,请手动引入样式。
module.exports = {
  plugins: [
    [
      'import',
      {
        libraryName: 'vant',
        libraryDirectory: 'es',
        // 指定样式路径
        style: (name) => `${name}/style/less`,
      },
      'vant',
    ],
  ],
};
  1. 修改样式变量,如果less-loader报错,把版本降到@5.0.0
// vue.config.js
module.exports = {
  css: {
    loaderOptions: {
      less: {
        // 若 less-loader 版本小于 6.0,请移除 lessOptions 这一级,直接配置选项。
        lessOptions: {
          modifyVars: {
            // 直接覆盖变量
            'text-color': '#111',
            'border-color': '#eee',
            // 或者可以通过 less 文件覆盖(文件路径为绝对路径)
            hack: `true; @import "your-less-file-path.less";`,
          },
        },
      },
    },
  },
};

路由处理

  1. 在router文件夹下创建module文件夹,页面的路由都放里面
// home.js
export default [
    {
        path: '/home',
        name: 'home',
        component: () => import('../../views/Home.vue'),
        meta: {
            title: '首页',
            showNav: true, // 是否显示导航栏
            showTabbar: false,
            backPath: 'app'
        }
    }
]
  1. 配置layout页面
// layout.vue
<template>
  <div class="layout">
    <van-nav-bar
      v-if="$route.meta.showNav"
      :title="$route.meta.title"
      left-text="返回"
      left-arrow
      class="layout__nav-bar"
    >
      <template #left>
        <van-icon name="arrow-left" size="18" @click="onClickLeft" />
      </template>
      <template #right>
        <van-icon name="search" size="18" @click="onClickRight" />
      </template>
    </van-nav-bar>
    <div class="app-content">
      <router-view v-if="$route.meta.keepAlive"></router-view>
    </div>
    <van-tabbar
      v-model="active"
      v-if="$route.meta.showTabbar"
      :placeholder="true"
    >
      <van-tabbar-item icon="wap-home-o">工作台</van-tabbar-item>
      <van-tabbar-item icon="orders-o">工单</van-tabbar-item>
      <van-tabbar-item icon="user-o">我的</van-tabbar-item>
    </van-tabbar>
  </div>
</template>
<script>
import { ref } from 'vue'
import { NavBar, Tabbar, TabbarItem } from 'vant'
export default {
  components: {
    [NavBar.name]: NavBar,
    [Tabbar.name]: Tabbar,
    [TabbarItem.name]: TabbarItem
  },
  setup () {
    const active = ref(0)
    const onClickLeft = () => { console.log('返回') }
    const onClickRight = () => console.log('右边按钮')
    return {
      active,
      onClickLeft,
      onClickRight,
    }
  },
};
</script>
<style lang="less" scoped>
.layout {
  height: 100vh;
  overflow: auto;
  background: #f0f0f0;
  .layout-bar {
  }
  .app-content {
  }
}
</style>
  1. 配置router文件夹下的index.js
import { createRouter, createWebHistory } from "vue-router"
import Index from '../views/index.vue'
const routerContext = require.context('./modules', true, /\.js$/)
const IndexRoute = {
  path: '/',
  component: Index,
  meta: {},
  children: []
}
routerContext.keys().forEach(route => {
  if (route.startsWith('./index')) {
    return
  }
  const routerModule = routerContext(route)
  IndexRoute.children = [...IndexRoute.children, ...(routerModule.default || routerModule)]
})
let routes = [
  IndexRoute
]
const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes: routes
})
export default router

icon图标处理

  1. 安装依赖:
yarn add svg-sprite-loader --dev
  1. 引入svg文件,在src下的assets文件夹下的icons文件夹下创建svg文件夹,里面放svg文件
  2. 创建svg-icon组件
// 在scr下的component文件夹下创建SvgIcon文件夹创建index.js
import { defineComponent, computed } from "vue"
import "./style.less"
export default defineComponent({
  name: "SvgIcon",
  props: {
    iconClass: {
      type: String,
      required: true
    },
    className: {
      type: String,
      default: ''
    }
  },
  setup (props) {
    const className = computed(() => {
      if (props.className) {
        return `svg-icon ${props.className}`
      } else {
        return "svg-icon"
      }
    })
    const icon = computed(() => {
      return `#icon-${props.iconClass}`
    })
    return () => (
      <svg class={className.value} aria-hidden="true">
        <use xlinkHref={icon.value} />
      </svg>
    )
  }
})
//style.less样式文件
.svg-icon {
    width: 1em;
    height: 1em;
    vertical-align: -0.15em;
    fill: currentColor;
    overflow: hidden;
}  
  1. 在main.js中处理引入并全局注册
import { createApp } from "vue"
import App from "./App.vue"
import "./registerServiceWorker"
import router from "./router"
import store from "./store"
import { Icon } from 'vant'
// svg处理
import SvgIcon from '@/components/SvgIcon'// svg组件
const requireAll = requireContext => requireContext.keys().map(requireContext)
const req = require.context('./assets/icons/svg', false, /\.svg$/)
requireAll(req)
createApp(App)
  .use(Icon)
  .use(store)
  .use(router)
  .component('svg-icon', SvgIcon)
  .mount("#app")

ajax请求处理

  1. 安装axios, yarn add axios --save
  2. 在src下创建utils文件夹,里面创建request.js
import axios from 'axios'
let baseURL = ''
// 根据环境,配置请求链接
switch (process.env.NODE_ENV) {
    case 'prod':
        baseURL = 'https://gateway.shenzhenpoly.com/customer-service-api'
        break
    case 'test':
        baseURL = 'https://api-gateway.shenzhenpoly.com/customer-service-api'
        break
    default:
        baseURL = 'https://api-gateway.shenzhenpoly.com/customer-service-api'
        break
}

const service = axios.create({
    timeout: 60 * 1000,// 超时时间
    baseURL: baseURL// 请求地址
})
// 设置请求体类型
service.defaults.headers.post['Content-Type'] = 'application/json;charset=UTF-8'
// 设置请求拦截器
service.interceptors.request.use(config => {
    return config
}, err => {
    return Promise.reject(err)
})
// 设置响应拦截器
service.interceptors.response.use(res => {
    return Promise.resolve(res)
    // 这里统一处理接口返回的错误
    // return Promise.reject(err)
}, err => {
    return Promise.reject(err)
})
export default service
  1. 在src下创建api文件夹,创建home.js
import request from '../utils/request'
// 获取用户信息
export function getUserInfo (data) {
    return request({
        url: '/user/loginUserInfo',
        method: 'POST',
        data: data,
    })
}
  1. 在组件中使用
<script>
import { onMounted } from 'vue'
import { Swipe, SwipeItem } from 'vant'
import user from './components/user'
import { getUserInfo } from './../../api/home'
export default {
  name: "Home",
  components: { user, [Swipe.name]: Swipe, [SwipeItem.name]: SwipeItem },
  setup () {
    async function getList () {
      let { data } = await getUserInfo({})
      console.log(data, 'data')
    }
    onMounted(() => {
      getList()
    })
  }
};
</script>

附上demo地址

gitee.com/lanbin1017/…