【第二弹】基于 Vue3 全家桶、TS/JS、Vite 构建工具,开箱即用的移动端项目🌱 基于 Vue3 全家桶、TS
🌱 基于 Vue3 全家桶、TS/JS、Vite 构建工具,开箱即用的移动端项目基础模板
各位知友,基于第一弹基于 Vue3和Webpack5 移动端脚手架的完成,用通俗易懂的方式和文案,和各位知友分享前端基础的脚手架构建~
第一阶段目前实现以下的优化和封装:
- ⚡ Vue3 + Vite5 + Vuex
- 🍕 TypeScript
- ✨ 全局环境变量
- 🎨 Vant4 组件库
- 🎊 vw 窗口适配
- 🌀 Tailwindcss 原子类框架
- 🌈 Pettier+ ESLint 统一代码风格
- 👏 页面标题自动切换
- 🚀 自动化部署
- 🕹 Mock 实现数据模拟
- 🎁 封装Axios请求库
- 🧭 封装Utils常用工具函数
- 🍕 打包资源GZIP压缩
- 🏀 自定义全屏加载动画 loading
- 🧸 浏览器回退Keep-Alive 页面状态保持
- 🌈 主题切换
有图有真相
效果
废话不多说,先上代码库 Gitee
目录结构
├── dist
├── public
├── src
│ ├── api
│ ├── assets
│ ├── components
│ ├── layout
│ ├── types
│ ├── router
│ ├── stores
│ ├── styles
│ ├── utils
│ ├── views
│ ├── App.vue
│ ├── main.ts
│ └── shims-vue.d.ts
├── .env
├── .env.development
├── .env.development-fix
├── .env.production
├── .gitignore
├── index.html
├── .prettierrc.js
├── postcss.config.js
├── tsconfig.json
├── tailwind.config.js
├── package.json
├── vite.config.ts
└── README.md
快速开始
# 安装依赖
npm install
# 或者
yarn
# 启动开发环境
npm run dev
# 构建测试环境
npm run build-fix
# 构建生产环境
npm run build
功能介绍
全局环境变量
- 配置全局环境变量,统一管理项目中的环境变量,包括开发环境、测试环境、生产环境等。
- package.json 中的 scripts 字段中添加命令,方便快速切换环境变量。
"scripts": {
"dev": "vite",
"build": "run-p type-check \"build-only-production {@}\"",
"build-fix": "run-p type-check \"build-only-development-fix {@}\"",
"preview": "vite preview",
"build-only-production": "cross-env NODE_ENV=production vite build --mode production",
"build-only-development-fix": "cross-env NODE_ENV=production vite build --mode development-fix",
"type-check": "vue-tsc --build --force",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"format": "prettier --write src/"
}
Vant4 组件库
- 使用 Vant4 组件库,轻松实现移动端项目的 UI 效果。
- 组件库文档:vant-ui.github.io/vant/#/zh-C…
Vant官方教程很详细,按步骤进行即可,这里不再赘述。
需要注意的点在于:
- Viewport布局下,postcss-px-to-viewport 会导致 deprecated 的警告,需要安装 cnjm-postcss-px-to-viewport 进行替换。
- 注意Vant的组件样式,对于750的设计稿需要单独处理
Tailwindcss 原子类框架
- 执行 npx tailwindcss init 命令后,项目根目录下会生成 tailwind.config.ts 配置文件,可以根据项目需求,修改配置文件中的内容,一般使用默认配置。
Pettier+ ESLint 统一代码风格
- 工程中使用的插件还是几位老演员,eslint、prettier;
- 需要结合开发工具的配置同时使用,开发效果杠杠的;有兴趣的知友小伙伴可以私信我,我把vscode的配置文件分享;
vw 窗口适配
移动端适配的方案,参考了Vant4 组件库的进阶用法,同时需要配置 postcss.config.js 配置文件;这里需要注意的点是,原来使用px转rem的方案,需要替换为px转vmin;主要是考虑一下几点原因哈:
- 可以实现动态的屏幕适应;因为vmin是基于视口的最小维度,能够自适应不同的屏幕尺寸和方向,无论是横屏还是竖屏,元素都能保持相对一致的比例;
- 可以简化设计;使用vmin可以减少在不同屏幕和设备上的媒体查询需求,因为它可以在不同的条件下自动调整大小,使得设计上更加灵活;
- 提高可读性;在一些情况下,字体文本、图形的大小使用vmin可以更好地保持和视口的比例,提升在不同设备上的可读性和可视性;
- 简化css代码;可以减少需要编写的css样式规则,降低维护成本,特别是在复杂的布局中;
综上所述,如果是需要基于字体大小进行整体调整,使用rem更为合适;如果是需要确保在不同屏幕尺寸上保持比例和布局一致,使用vmin更加灵活;
页面标题自动切换
这个就相对简单了,逻辑和代码实现都是最基础的;下面就简要结合关键代码和实现思路,和各位知友分享;
- 第一步,借助 vue-router的自定义属性字段 meta,定义需要自定义页面标题的路由文件,自定义title;
import { type RouteRecordRaw } from 'vue-router';
export const routes: RouteRecordRaw[] = [
{
path: '/',
name: 'root',
component: () => import('../layout/index.vue'),
redirect: '/home',
children: [
{
path: '/home',
name: 'home',
component: () => import('../views/home/index.vue'),
meta: {
title: '首页',
keepAlive: true
}
},
{
path: '/settings',
name: 'settings',
component: () => import('../views/settings/index.vue'),
meta: {
title: '工具'
}
},
{
path: '/about',
name: 'about',
component: () => import('../views/about/index.vue'),
meta: {
title: '关于'
}
}
]
}
];
- 第二步,在vue-router的路由劫持中,设置页面标题;
router.beforeEach((to: toRouteModel, from, next) => {
// 设置页面标题
setPageTitle(to.meta.title);
next();
});
这里简单封装了一个设置页面标题的方法体 setPageTitle;通过自定义全局变量,设置了一个默认的页面标题;
export function setPageTitle(routeTitle?: string) {
window.document.title = routeTitle
? `${routeTitle}`
: import.meta.env.VITE_APP_DEFAULT_TITLE;
}
Mock 实现数据模拟
各位知友也都清楚,现在项目的开发模式基本上是前后端分离,作为前端开发工程师,既要实现页面效果,也要负责对接业务数据API,拉通业务流程;往往页面效果需要真实的数据填充,然而我们不能纯依靠后端小伙伴把接口实现了再去coding;所以,前端结合接口文档进行数据的模拟就显得非常重要了;这样既可以让前端和后端团队可以在各自的工作上进行平行开发,减少了等待后端完成开发和部署的时间,提高了整体的开发效率,又可以帮助前端开发人员创建各种场景(如错误响应、延迟等)来测试前端的处理逻辑,确保应用在不同情况下的稳定性和可靠性;
具体实现的思路如下:
- 安装 mockjs ,vite-plugin-mock-dev-server 插件
yarn add mockjs, vite-plugin-mock-dev-server -D
- 配置 vite.config.ts 文件
主要是配置本地Mock数据的代理请求,引入 vite-plugin-mock-dev-server 插件
// 引入 vite-plugin-mock-dev-server 插件
export default defineConfig(({ mode }) => {
return defineConfig({
plugins: [
mockDevServerPlugin()
]
server: {
host: true,
proxy: {
'^/dev-api': {
target: ''
}
}
}
})
})
- 编写接口请求
假如我们模拟一个数据列表请求,这里没什么需要解释的,mockjs的规则,可以去看官网,我直接贴代码了;
import { defineMock } from 'vite-plugin-mock-dev-server';
import Mock from 'mockjs';
export default defineMock([
{
url: '/dev-api/list/get',
delay: 1000,
body: {
code: 0,
message: 'OK',
data: Mock.mock({
'list|10': [
{
'id|+1': 1,
name: '@cname',
'age|1-100': 1,
email: '@email',
address: '@county(true)',
phone: /^1[34578]\d{9}$/
}
],
total: 10
})
}
}
]);
- 模拟请求
接下来就是和正常请求接口一样,实现接口请求的逻辑;
这里我们使用了封装的axios自定义,后面会说到
import Http from '../utils/http/index';
export const getLists = () => {
return Http.request({
url: `/list/get`,
method: 'get'
});
};
这样,我们就可以正常的进行数据模拟了,是不是很Easy;主要是各位前端开发小伙伴要有这种意识,提高编码的质量和效率,剩下的时间就可以提升自己(摸鱼)了。
封装Axios请求库
这个就比较复杂了,就需要考量系统性、细节性的编码逻辑,我也是抽了一晚上的烟,才整体的撸出来,不过是通用的,每个项目中都可以直接cv,我只是比较懒,没有封装为npm包文件;
下面我也只是讲下思路,具体的代码各位知友可以去看代码仓库;
- 第一步,首先我们是基于axios的通用库进行的二次封装,这里就要考虑请求前的拦截处理、请求后的拦截处理、重复并发请求处理、异常结果处理、和一些特定数据自定义处理的逻辑;前面的都好理解,那特定数据自定义处理怎么理解呢?这里我给出了项目开发过程中的一些自定义的配置,大家可以参考
export interface RequestOptions {
// 请求参数拼接到url
joinParamsToUrl?: boolean;
// 是否格式化请求参数时间;
formatDate?: boolean;
// 是否格式化出参
isTransformRequestResult?: boolean;
// 请求方式
contentType?: string;
// 是否解析成JSON
isParseToJson?: boolean;
// 是否统一处理Http异常信息
isShowErrorMessage?: boolean;
// http错误信息的文本
errorMessageText?: string;
// 是否统一处理业务异常信息
isShowServerErrorMessage?: boolean;
// 业务错误信息的文本
serverErrorMessage?: string;
// 是否设置请求超时
isTimeout?: boolean;
// 请求超时时长(毫秒)
timeoutNumber?: number;
// 是否忽略token
ignoreToken?: boolean;
// 是否忽略loading
ignoreLoading?: boolean;
// 是否忽略重复请求
ignorePendingRequest?: boolean;
}
当然,上面的配置都是具备默认设置的,不会每次请求都需要配置,那不就弄巧成拙了,哈哈哈...
打包资源GZIP压缩
这个就相对简单了,主要是使用插件 vite-plugin-compression的配置,大家可以在插件包说明文档中查看,这里不再赘述;
自定义全屏加载动画 loading
这个效果的实现,我个人还是比较得意的,这里详细的给各位知友分享下; 首先,各种组件库都已经提供了loading的效果,但是现在的用户哈,对视觉比较挑剔,需要一些个性化的效果来满足;
- 首先,定义一个自定义loading的容器
<Teleport to="body">
<div class="spinner-container" v-show="loading.loading">
<div class="spinner-icon"></div>
<div class="spinner-text text-[26px] mt-[24px] text-black font-bold">
{{ loading.text }}
</div>
</div>
</Teleport>
这里使用了 vue3的新特性 Teleport,主要是考虑loading整体遮罩的问题,同时避免样式污染;
- 下一步,实现一段自定义loading的样式
.spinner-container {
height: 100vh;
width: 100vw;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: var(--color-border);
position: fixed;
top: 0;
left: 0;
pointer-events: none;
.spinner-icon {
position: relative;
width: 88px;
height: 88px;
background-color: var(--van-tabbar-item-active-color);
animation: cube-shadow-spinner 1.8s cubic-bezier(0.75, 0, 0.5, 1) infinite;
}
}
@keyframes cube-shadow-spinner {
50% {
border-radius: 50%;
transform: scale(0.5) rotate(360deg);
}
100% {
transform: scale(1) rotate(720deg);
}
}
- 下一步就通过vuex状态机来控制loading效果的显隐
interface ILoadingStateModel {
loading: boolean;
text?: string;
}
const state = () => ({
loading: false,
text: ''
});
const getters = {
getLoading(state: ILoadingStateModel) {
return {
loading: state.loading,
text: state.text
};
}
};
const mutations = {
SET_LOADING(
state: ILoadingStateModel,
loading: ILoadingStateModel | boolean
) {
if (typeof loading === 'boolean') {
state.loading = loading;
} else {
state.loading = loading.loading;
if (loading.loading && loading.text) {
state.text = loading.text;
}
}
}
};
export default {
namespaced: true,
getters,
mutations,
state
};
这样在具体使用的场景下,就可以调用 SET_LOADING 来控制loading的显隐和提示文本了。
主题切换
白天/黑夜的主题切换,目前前端有比较成熟的方案,结合一些特效实现效果,也是一些前端大拿比较喜欢炫的技能,其实实用性倒不是很大,所以在这里就不再赘述了,感兴趣的知友小伙伴可以私信我一起讨论,或者去代码仓库中查看;
本地svg图片加载
不知道各位知友小伙伴是不是遇到过,我每次加载svg本地图片的时候,总是遇到各种各样的问题,导致svg无法渲染;这里主要使用的是 vite-plugin-svg-icons插件,实现本地svg图片的动态加载;我来详细的记录下,也是作为自己的一个方案解决记录;
- 当然第一步还是安装插件 vite-plugin-svg-icons
yarn add vite-plugin-svg-icons -D
- 在 vite.config.ts中配置
注意,这里的iconDirs的路径,一定是要和你工程中svg存放的路径保持一致
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
// 当前工作目录路径
const root: string = process.cwd();
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, root);
return defineConfig({
plugins: [
createSvgIconsPlugin({
symbolId: 'icon-[dir]-[name]',
iconDirs: [resolve(root, 'src/assets/icons')]
})
]
})
})
- 就是封装一个 svg-icon 的组件,接收一些通用的配置参数,这里可以根据项目需求调整哈。
这里切记,切记,切记,sumbolId一定要添加 #,我也是排查定位了好久,才找到了问题,太坑了(自己SB了)
<script setup lang="ts" name="ISvgIcon">
import { defineProps, computed } from 'vue';
const props = defineProps({
name: {
type: String,
required: true,
default: ''
}, // 图标名称
prefix: {
type: String,
default: 'icon'
}, // 前缀
color: {
type: String,
default: '#333333'
}, // 颜色
className: {
type: String,
default: ''
} // 大小
});
// svg 图标名称
const symbolId = computed(() => `#${props.prefix}-${props.name}`);
</script>
<template>
<svg aria-hidden="true" :class="props.className">
<use :href="symbolId" :fill="props.color" />
</svg>
</template>
- 还有一步不要忘记了,在main.ts文件中,添加一段配置,也是插件要求的;
把svg注册
import 'virtual:svg-icons-register';
- 如果需要把组件注册为全局组件,就使用vue组件注册的方式;
const app = createApp(App);
app.component('i-svg-icon', ISvgIcon);
这样,就可以全局使用i-svg-icon的全局组件了;
其实方案很简单,只是会有一些细节的问题点需要知友注意的。
好啦,各位知友,今天的分享就到这里了;后期我也会不断地完善更新。
赠人玫瑰,手有余香 各位知友如果有更好的想法,欢迎各位知友留言呀,我期待和各位知友互动讨论👨💻👨💻👨💻👨💻👨💻👨💻,接收大家的指导和鼓励👏👏👏👏👏
转载自:https://juejin.cn/post/7424334647570169908