当前位置: 首页>后端>正文

vue3搭建移动端项目

本项目搭建适用于h5移动端的vue项目搭建,主体是基于vue-cli3脚手架,目的在于搭建个可用于快速启动项目的基础框架。话不多说,马上动手搭建:

技术桟:vue3,pinia,vant,vite,axios
注意vue3对node有要求

1.新建项目

npm init vue@latest

一些配置选项


vue3搭建移动端项目,第1张
创建项目

项目名不要带vant,图写多了

安装less

npm i less less-loader -D

vue3搭建移动端项目,第2张

vue3搭建移动端项目,第3张

修改一下vite配置
引入const path = require('path');

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
const path = require('path');

// https://vitejs.dev/config/
// export default defineConfig({
//   plugins: [vue(), vueJsx()],
//   resolve: {
//     alias: {
//       '@': fileURLToPath(new URL('./src', import.meta.url))
//     }
//   }
// })
export default ({ mode }) => {
  // https://vitejs.dev/config/
  return defineConfig({
    plugins: [vue(), vueJsx()],
    resolve: {
      alias: [
        {
          find: '@',
          replacement: path.resolve(__dirname, 'src'),
        },
      ],
      // 支持vue后缀名
      extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
    },
    css: {
      preprocessorOptions: {
        less: {
          javascriptEnabled: true,
          additionalData: `@import "${path.resolve(__dirname, 'src/assets/config.less')}";`, // 全局引入less配置
        },
      }
    },
  });
}

添加公共less文件config.less或者直接改了base.css也行


vue3搭建移动端项目,第4张
// config.less
@primary-color: #1890ff; // 主色

// main.less
#app {
  width: 100%;
  min-height: 100%;
  min-width: 320px;
  max-width: 750px;
  margin: 0 auto;
  color: #333;
  background: #f2f2f2;
}

@media screen and (min-width: 750px) {
    html {
        font-size: 352px !important;
        /*no*/
    }
}

main.css改main.less 其他无关样式可以删了 ,main.js记得也改后缀
@import './config.less';

安装vant

npm i vant -S

// main.js
// vant样式
// Toast
import 'vant/es/toast/style';
// Dialog
import 'vant/es/dialog/style';
// Notify
import 'vant/es/notify/style';
// ImagePreview
import 'vant/es/image-preview/style';

// 错误监控
app.config.errorHandler = (err, vm) => {
  let { message, name, stack } = err;

  if (message || name) {
    //   errReport({
    //     target: vm?.$options?.__name,
    //     msg: stack stack.toString() : `${name}:${message}`,
    //     user: ''
    //   });
  }
  console.error(err);
};

安装 unplugin-vue-components

可自动引入src/components的组件,以及vant组件
npm i unplugin-vue-components -D

// vite.config.js
import Components from 'unplugin-vue-components/vite';
import { VantResolver } from 'unplugin-vue-components/resolvers';

...
plugins: [
            vue(),
            vueJsx(),
            Components({
                resolvers: [VantResolver()],
                extensions: ['vue'], //文件扩展
                // 配置type文件生成位置
                dts: 'src/components.d.ts',
            }),
        ],

安装unplugin-auto-import

自动导入vue3api

npm i -D unplugin-auto-import

安装amfe-flexible + postcss-pxtorem 完成移动端的rem布局适配,Autoprefixer 浏览器前缀处理工具

npm i postcss postcss-pxtorem amfe-flexible autoprefixer -D
npm i amfe-flexible -S

修改vite.config.js

import autoprefixer from 'autoprefixer';
import postCssPxToRem from 'postcss-pxtorem';
...
css: {
      preprocessorOptions: {
        less: {
          javascriptEnabled: true,
          additionalData: `@import "${path.resolve(__dirname, 'src/assets/config.less')}";`,
        },
      },
      postcss: {
        plugins: [
          autoprefixer({
            overrideBrowserslist: ['Android 4.1', 'iOS 7.1', 'Chrome > 31', 'ff > 31', 'ie >= 8'],
          }),
          postCssPxToRem({
            rootValue({ file }) {
              return file.indexOf('vant') !== -1 37.5 : 75; // vant框架
            },
            propList: ['*'], // 需要转换的属性,这里选择全部都进行转换
            selectorBlackList: ['norem'], // 过滤掉norem-开头的class,不进行rem转换
          }),
        ],
      },
    },

main.js引入import 'amfe-flexible';

安装axios, qs

npm i axios qs -S
src下创建lib文件夹
创建request.js

import axios from 'axios';
import { showToast } from 'vant';

// 创建axios实例
const service = axios.create({
  timeout: 60 * 1000, // 请求超时时间
});
const errorHandler = (e, report = true) => {
  const res = e?.response?.data;
  const msgStr = res?.msg || res?.errmsg || res?.message;
  const errData = {
    msg: msgStr,
  };
  const reqData = e.config.data;
  reqData && Object.assign(errData, { data: reqData });
  showErrMsg(msgStr);
  throw e;
};
const showErrMsg = (msgStr) => {
  showToast({
    message: msgStr || '网络错误',
    icon: 'warning-o',
  });
};

// request拦截器
service.interceptors.request.use(
  (config) => {
    return config;
  },
  (error) => {
    errorHandler(error);
  }
);
// response 拦截器
service.interceptors.response.use(
  (response) => {
    const res = response.data;
    if (res?.code) {
      if ([1, 200].includes(res.code)) {
        return res?.data;
      } else {
        let msgStr = res?.msg || '抱歉,系统错误';
        showErrMsg(msgStr);
      }
    } else {
      // 响应体为数据,无code
      return res;
    }
  },
  (error) => {
    errorHandler(error);
  }
);

const get = (url, params, options) => {
  return service.get(url, {
    params,
    ...options,
  });
};
const post = (url, body, headers = {}) => {
  return service.post(url, body, {
    headers,
  });
};
const remove = (url, body) => {
  return service.delete(url, { data: body });
};
const put = (url, body, headers = {}) => {
  return service.put(url, body, {
    headers,
  });
};

export default { get, post, remove, put };

创建apiPath.js

const isProd = !/sandbox|localhost|127\.0.0.1|10.1./i.test(location.hostname);
const BASE_URL = isProd '' : '';

export default  {
  WELCOME: BASE_URL +'/api/。。。',
};

其他修改项

1.router/index.js

history: createWebHistory(import.meta.env.BASE_URL),
history: createWebHistory('/'),// 改这个

import.meta.env.BASE_URL虽然默认'/'但vite里的base配置会改动到他,所以还是写死好。

2.移动端配置

<meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta
      name="viewport"
      content="minimum-scale=1.0, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
    />
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <meta name="apple-touch-fullscreen" content="yes" />

3.package.json修改

"scripts": {
    "dev": "vite",
    "build": "vite build --mode development",
    "prod": "vite build --mode production",
    "preview": "vite preview"
  },

根目录添加环境文件


vue3搭建移动端项目,第5张

分别写上

VITE_ENV = 'development'
VITE_ENV = 'production'

vite配置

import { defineConfig, loadEnv } from 'vite';

export default ({ mode }) => {
const isProd = loadEnv(mode, process.cwd()).VITE_ENV === 'production';
    // https://vitejs.dev/config/
    return defineConfig({
        base: isProd '/' : '/',

      build: {
      chunkSizeWarningLimit: 1000, // 提高超大静态资源警告大小
      // sourcemap: true,

      rollupOptions: {
        input: 'index.html',
        output: {
          // 静态资源打包做处理
          chunkFileNames: 'static/js/[name]-[hash].js',
          entryFileNames: 'static/js/[name]-[hash].js',
          assetFileNames: 'static/[ext]/[name]-[hash].[ext]',
          manualChunks: {
            // 分包
            vue: ['vue', 'vue-router', 'pinia'],
            vant: ['vant'],
          },
        },
      },
    },
....

4.iPhoneX安全区间问题

<meta
      name="viewport"
      content="minimum-scale=1.0, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
    />

viewport-fit 默认有3个值
contain:可视窗口完全包含网页内容
cover:网页内容完全覆盖可视窗口
auto:默认值,此值不影响初始布局视图端口,并且整个web页面都是可查看的。

Webkit的css函数,env()和constant(),是IOS11新增特性,用于设定安全区域与边界的距离,有4个预定义变量

safe-area-inset-left:安全区域距离左边边界的距离 // 竖屏为0
safe-area-inset-right:安全区域距离右边边界的距离 // 竖屏为0
safe-area-inset-top:安全区域距离顶部边界的距离 // 为导航栏+状态栏的高度 88px
safe-area-inset-bottom :安全距离底部边界的距离 // 34px

使用,constant和env按顺序写,可叠加calc函数使用

height: constant(safe-area-inset-bottom);
height: env(safe-area-inset-bottom);

完整vite配置

import { defineConfig, loadEnv } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import Components from 'unplugin-vue-components/vite';
import { VantResolver } from 'unplugin-vue-components/resolvers';
import AutoImport from 'unplugin-auto-import/vite';
import autoprefixer from 'autoprefixer';
import postCssPxToRem from 'postcss-pxtorem';
const path = require('path');

export default ({ mode }) => {
  const isProd = loadEnv(mode, process.cwd()).VITE_ENV === 'production';
  // https://vitejs.dev/config/
  return defineConfig({
    plugins: [
      vue(),
      vueJsx(),
      Components({
        resolvers: [VantResolver()],
        extensions: ['vue'], //文件扩展
        // 配置type文件生成位置
        dts: 'src/components.d.ts',
      }),
      AutoImport({
        include: [
          /\.[tj]sx?$/, // .ts, .tsx, .js, .jsx
          /\.vue$/,
          /\.vue\?vue/, // .vue
          /\.md$/, // .md
        ],
        imports: [
          'vue',
          'vue-router',
          'pinia',      
        ],
      }),
    ],
    base: isProd '/' : '/',
    server: {
      host: '0.0.0.0',
      port: 8080,
      proxy: {
        '/api': {
          target: '',
          changeOrigin: true,
          rewrite: (path) => path.replace(/^\/api/, ''),
        },
      },
    },
    build: {
      chunkSizeWarningLimit: 1000, // 提高超大静态资源警告大小
      // sourcemap: true,

      rollupOptions: {
        input: 'index.html',
        output: {
          // 静态资源打包做处理
          chunkFileNames: 'static/js/[name]-[hash].js',
          entryFileNames: 'static/js/[name]-[hash].js',
          assetFileNames: 'static/[ext]/[name]-[hash].[ext]',
          manualChunks: {
            // 分包
            vue: ['vue', 'vue-router', 'pinia'],
            vant: ['vant'],
          },
        },
      },
    },
    resolve: {
      alias: [
        {
          find: '@',
          replacement: path.resolve(__dirname, 'src'),
        },
      ],
      // 支持vue后缀名
      extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
    },
    css: {
      preprocessorOptions: {
        less: {
          javascriptEnabled: true,
          additionalData: `@import "${path.resolve(__dirname, 'src/assets/config.less')}";`,
        },
      },
      postcss: {
        plugins: [
          autoprefixer({
            overrideBrowserslist: ['Android 4.1', 'iOS 7.1', 'Chrome > 31', 'ff > 31', 'ie >= 8'],
          }),
          postCssPxToRem({
            rootValue({ file }) {
              return file.indexOf('vant') !== -1 37.5 : 75;
            },
            propList: ['*'], // 需要转换的属性,这里选择全部都进行转换
            selectorBlackList: ['norem'], // 过滤掉norem-开头的class,不进行rem转换
          }),
        ],
      },
    },
  });
};


https://www.xamrdz.com/backend/38a1931361.html

相关文章: