从0到1搭建Vue3+Vant移动端项目(二)

631 阅读1分钟

8. 集成Vant组件库

安装Vant:

npm i vant@next -S

安装按需加载插件:

npm i vite-plugin-style-import -D

配置vite.config.js

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
import styleImport, { VantResolve } from 'vite-plugin-style-import'
import postCssPxToRem from 'postcss-pxtorem'

// vite.config.js 继续
export default defineConfig({
  plugins: [
    vue(),
    styleImport({
      resolves: [VantResolve()],
      libs: [
        {
          libraryName: 'vant',
          esModule: true,
          resolveStyle: (name) => `vant/es/${name}/style/index`
        }
      ]
    })
  ],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src')
    }
  },
  server: {
    port: 3000,
    open: true,
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  },
  css: {
    postcss: {
      plugins: [
        postCssPxToRem({
          rootValue: 37.5, // Vant 官方根字体大小是 37.5
          propList: ['*'],
          selectorBlackList: ['.norem'] // 过滤掉.norem-开头的class,不进行rem转换
        })
      ]
    }
  },
  build: {
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true
      }
    },
    rollupOptions: {
      output: {
        manualChunks: {
          vant: ['vant'],
          vendor: ['vue', 'vue-router', 'vuex']
        }
      }
    }
  }
})

9. 移动端适配

创建src/utils/flexible.js文件来处理移动端适配:

(function flexible(window, document) {
  const docEl = document.documentElement
  const dpr = window.devicePixelRatio || 1

  // 调整body标签的fontSize
  function setBodyFontSize() {
    if (document.body) {
      document.body.style.fontSize = '16px'
    } else {
      document.addEventListener('DOMContentLoaded', setBodyFontSize)
    }
  }
  setBodyFontSize()

  // 设置根元素fontSize
  function setRemUnit() {
    const rem = docEl.clientWidth / 10
    docEl.style.fontSize = rem + 'px'
  }

  setRemUnit()

  // 当页面大小变化时,重新设置rem
  window.addEventListener('resize', setRemUnit)
  window.addEventListener('pageshow', function(e) {
    if (e.persisted) {
      setRemUnit()
    }
  })

  // 当设备屏幕支持0.5px时,设置meta标签的viewport
  if (dpr >= 2) {
    const fakeBody = document.createElement('body')
    const testElement = document.createElement('div')
    testElement.style.border = '.5px solid transparent'
    fakeBody.appendChild(testElement)
    docEl.appendChild(fakeBody)
    if (testElement.offsetHeight === 1) {
      docEl.classList.add('hairlines')
    }
    docEl.removeChild(fakeBody)
  }
}(window, document))

main.js中引入:

import './utils/flexible'

10. 全局样式和组件

创建全局样式文件src/styles/index.scss

// 引入重置样式
@import './reset.scss';
@import './variables.scss';
@import './mixin.scss';

// 全局样式
body {
  height: 100%;
  -moz-osx-font-smoothing: grayscale;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
}

html {
  height: 100%;
  box-sizing: border-box;
}

// 滚动条样式
::-webkit-scrollbar {
  width: 6px;
  height: 6px;
}

::-webkit-scrollbar-track {
  background: #f1f1f1;
  border-radius: 3px;
}

::-webkit-scrollbar-thumb {
  background: #ccc;
  border-radius: 3px;
}

::-webkit-scrollbar-thumb:hover {
  background: #aaa;
}

// 清除浮动
.clearfix {
  &:after {
    content: "";
    display: table;
    clear: both;
  }
}

// 页面过渡动画
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}

// 安全区适配
.safe-area-bottom {
  padding-bottom: constant(safe-area-inset-bottom);
  padding-bottom: env(safe-area-inset-bottom);
}

创建src/styles/reset.scss

/* 基本样式重置 */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}

html, body {
  width: 100%;
  height: 100%;
  user-select: none;
}

a {
  text-decoration: none;
  color: inherit;
}

img {
  vertical-align: middle;
  width: 100%;
}

input, button, textarea {
  border: none;
  outline: none;
  background: none;
}

ul, ol, li {
  list-style: none;
}

h1, h2, h3, h4, h5, h6 {
  font-weight: normal;
}

创建全局变量文件src/styles/variables.scss

// 颜色变量
$primary-color: #1989fa;
$success-color: #07c160;
$warning-color: #ff976a;
$danger-color: #ee0a24;
$info-color: #909399;

// 文字颜色
$text-color-primary: #303133;
$text-color-regular: #606266;
$text-color-secondary: #909399;
$text-color-placeholder: #c0c4cc;

// 边框颜色
$border-color-base: #dcdfe6;
$border-color-light: #e4e7ed;
$border-color-lighter: #ebeef5;
$border-color-extra-light: #f2f6fc;

// 背景颜色
$background-color-base: #f5f7fa;
$background-color-light: #f5f5f5;
$background-color-white: #ffffff;

// 字体大小
$font-size-extra-large: 20px;
$font-size-large: 18px;
$font-size-medium: 16px;
$font-size-base: 14px;
$font-size-small: 13px;
$font-size-extra-small: 12px;

// 间距
$spacing-extra-large: 32px;
$spacing-large: 24px;
$spacing-medium: 16px;
$spacing-base: 12px;
$spacing-small: 8px;
$spacing-extra-small: 4px;

// 圆角
$border-radius-base: 4px;
$border-radius-small: 2px;
$border-radius-large: 8px;
$border-radius-round: 20px;
$border-radius-circle: 50%;

// 阴影
$box-shadow-base: 0 2px 4px rgba(0, 0, 0, 0.12);
$box-shadow-light: 0 2px 12px 0 rgba(0, 0, 0, 0.1);

创建src/styles/mixin.scss

// 文本溢出显示省略号
@mixin ellipsis {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

// 多行文本溢出显示省略号
@mixin multi-ellipsis($line: 2) {
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: $line;
  -webkit-box-orient: vertical;
}

// flex布局
@mixin flex($justify: center, $align: center, $direction: row) {
  display: flex;
  justify-content: $justify;
  align-items: $align;
  flex-direction: $direction;
}

// 定位居中
@mixin position-center {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

// 宽高
@mixin size($width, $height: $width) {
  width: $width;
  height: $height;
}

// 一像素边框
@mixin hairline($position: bottom, $color: $border-color-base) {
  position: relative;
  
  &::after {
    content: '';
    position: absolute;
    
    @if $position == top {
      left: 0;
      top: 0;
      right: 0;
      height: 1px;
      transform: scaleY(0.5);
      border-top: 1px solid $color;
    }
    @else if $position == right {
      top: 0;
      right: 0;
      bottom: 0;
      width: 1px;
      transform: scaleX(0.5);
      border-right: 1px solid $color;
    }
    @else if $position == bottom {
      left: 0;
      right: 0;
      bottom: 0;
      height: 1px;
      transform: scaleY(0.5);
      border-bottom: 1px solid $color;
    }
    @else if $position == left {
      top: 0;
      left: 0;
      bottom: 0;
      width: 1px;
      transform: scaleX(0.5);
      border-left: 1px solid $color;
    }
    @else if $position == all {
      top: 0;
      left: 0;
      width: 200%;
      height: 200%;
      transform: scale(0.5);
      transform-origin: left top;
      border: 1px solid $color;
    }
  }
}