本项目搭建适用于h5移动端的vue项目搭建,主体是基于vue-cli3脚手架,目的在于搭建个可用于快速启动项目的基础框架。话不多说,马上动手搭建:
技术桟:vue3,pinia,vant,vite,axios
注意vue3对node有要求
1.新建项目
npm init vue@latest
一些配置选项
项目名不要带vant,图写多了
安装less
npm i less less-loader -D
修改一下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也行
// 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"
},
根目录添加环境文件
分别写上
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转换
}),
],
},
},
});
};