vue3搭建移動端項目

本項目搭建適用于h5移動端的vue項目搭建,主體是基于vue-cli3腳手架黔酥,目的在于搭建個可用于快速啟動項目的基礎框架跪者。話不多說,馬上動手搭建:

技術桟:vue3逗概,pinia柜蜈,vant,vite隶垮,axios
注意vue3對node有要求

1.新建項目

npm init vue@latest

一些配置選項


創(chuàng)建項目

項目名不要帶vant秘噪,圖寫多了

安裝less

npm i less less-loader -D

image.png

image.png

修改一下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也行


image.png
// 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';

// 錯誤監(jiān)控
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下創(chuàng)建lib文件夾
創(chuàng)建request.js

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

// 創(chuàng)建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 || '網(wǎng)絡錯誤',
    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 || '抱歉八千,系統(tǒng)錯誤';
        showErrMsg(msgStr);
      }
    } else {
      // 響應體為數(shù)據(jù)燎猛,無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 };

創(chuàng)建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"
  },

根目錄添加環(huán)境文件


image.png

分別寫上

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, // 提高超大靜態(tài)資源警告大小
      // sourcemap: true,

      rollupOptions: {
        input: 'index.html',
        output: {
          // 靜態(tài)資源打包做處理
          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安全區(qū)間問題

<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:可視窗口完全包含網(wǎng)頁內(nèi)容
cover:網(wǎng)頁內(nèi)容完全覆蓋可視窗口
auto:默認值,此值不影響初始布局視圖端口昌简,并且整個web頁面都是可查看的。

Webkit的css函數(shù)谦疾,env()和constant()犬金,是IOS11新增特性,用于設定安全區(qū)域與邊界的距離峰伙,有4個預定義變量

safe-area-inset-left:安全區(qū)域距離左邊邊界的距離 // 豎屏為0
safe-area-inset-right:安全區(qū)域距離右邊邊界的距離 // 豎屏為0
safe-area-inset-top:安全區(qū)域距離頂部邊界的距離 // 為導航欄+狀態(tài)欄的高度 88px
safe-area-inset-bottom :安全距離底部邊界的距離 // 34px

使用该默,constant和env按順序?qū)懀莎B加calc函數(shù)使用

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, // 提高超大靜態(tài)資源警告大小
      // sourcemap: true,

      rollupOptions: {
        input: 'index.html',
        output: {
          // 靜態(tài)資源打包做處理
          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轉換
          }),
        ],
      },
    },
  });
};

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末囊咏,一起剝皮案震驚了整個濱河市塔橡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌葛家,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件底燎,死亡現(xiàn)場離奇詭異双仍,居然都是意外死亡桌吃,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門逗物,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瑟俭,“玉大人,你說我怎么就攤上這事尔当⊥钟” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵缴阎,是天一觀的道長简软。 經(jīng)常有香客問我述暂,道長建炫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任艺配,我火速辦了婚禮衍慎,結果婚禮上,老公的妹妹穿的比我還像新娘稳捆。我一直安慰自己乔夯,他們只是感情好,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布镶苞。 她就那樣靜靜地躺著鞠评,像睡著了一般。 火紅的嫁衣襯著肌膚如雪剃幌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天牍白,我揣著相機與錄音抖棘,去河邊找鬼。 笑死最岗,一個胖子當著我的面吹牛朝捆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播驯用,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼记餐!你這毒婦竟也來了薇正?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎佳魔,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宁脊,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡贤姆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年霞捡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碧信。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡砰碴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出呈枉,到底是詐尸還是另有隱情,我是刑警寧澤酥泞,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布住册,位于F島的核電站,受9級特大地震影響凡人,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜传睹,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一岸晦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧邢隧,春花似錦冈在、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至侍郭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間汰寓,已是汗流浹背苹粟。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留毛好,地道東北人苛秕。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像吼驶,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蟹演,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容