likes
comments
collection
share

【第二弹】基于 Vue3 全家桶、TS/JS、Vite 构建工具,开箱即用的移动端项目🌱 基于 Vue3 全家桶、TS

作者站长头像
站长
· 阅读数 17

🌱 基于 Vue3 全家桶、TS/JS、Vite 构建工具,开箱即用的移动端项目基础模板

各位知友,基于第一弹基于 Vue3和Webpack5 移动端脚手架的完成,用通俗易懂的方式和文案,和各位知友分享前端基础的脚手架构建~

【第二弹】基于 Vue3 全家桶、TS/JS、Vite 构建工具,开箱即用的移动端项目🌱 基于 Vue3 全家桶、TS

第一阶段目前实现以下的优化和封装:

  • ⚡ Vue3 + Vite5 + Vuex
  • 🍕 TypeScript
  • ✨ 全局环境变量
  • 🎨 Vant4 组件库
  • 🎊 vw 窗口适配
  • 🌀 Tailwindcss 原子类框架
  • 🌈 Pettier+ ESLint 统一代码风格
  • 👏 页面标题自动切换
  • 🚀 自动化部署
  • 🕹 Mock 实现数据模拟
  • 🎁 封装Axios请求库
  • 🧭 封装Utils常用工具函数
  • 🍕 打包资源GZIP压缩
  • 🏀 自定义全屏加载动画 loading
  • 🧸 浏览器回退Keep-Alive 页面状态保持
  • 🌈 主题切换

有图有真相

【第二弹】基于 Vue3 全家桶、TS/JS、Vite 构建工具,开箱即用的移动端项目🌱 基于 Vue3 全家桶、TS

效果

废话不多说,先上代码库 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 组件库

Vant官方教程很详细,按步骤进行即可,这里不再赘述。

需要注意的点在于:

  • Viewport布局下,postcss-px-to-viewport 会导致 deprecated 的警告,需要安装 cnjm-postcss-px-to-viewport 进行替换。
  • 注意Vant的组件样式,对于750的设计稿需要单独处理

Tailwindcss 原子类框架

  1. 执行 npx tailwindcss init 命令后,项目根目录下会生成 tailwind.config.ts 配置文件,可以根据项目需求,修改配置文件中的内容,一般使用默认配置。

Pettier+ ESLint 统一代码风格

  1. 工程中使用的插件还是几位老演员,eslint、prettier;
  2. 需要结合开发工具的配置同时使用,开发效果杠杠的;有兴趣的知友小伙伴可以私信我,我把vscode的配置文件分享;

vw 窗口适配

移动端适配的方案,参考了Vant4 组件库的进阶用法,同时需要配置 postcss.config.js 配置文件;这里需要注意的点是,原来使用px转rem的方案,需要替换为px转vmin;主要是考虑一下几点原因哈:

  • 可以实现动态的屏幕适应;因为vmin是基于视口的最小维度,能够自适应不同的屏幕尺寸和方向,无论是横屏还是竖屏,元素都能保持相对一致的比例;
  • 可以简化设计;使用vmin可以减少在不同屏幕和设备上的媒体查询需求,因为它可以在不同的条件下自动调整大小,使得设计上更加灵活;
  • 提高可读性;在一些情况下,字体文本、图形的大小使用vmin可以更好地保持和视口的比例,提升在不同设备上的可读性和可视性;
  • 简化css代码;可以减少需要编写的css样式规则,降低维护成本,特别是在复杂的布局中;

综上所述,如果是需要基于字体大小进行整体调整,使用rem更为合适;如果是需要确保在不同屏幕尺寸上保持比例和布局一致,使用vmin更加灵活;

页面标题自动切换

这个就相对简单了,逻辑和代码实现都是最基础的;下面就简要结合关键代码和实现思路,和各位知友分享;

  1. 第一步,借助 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: '关于'
        }
      }
    ]
  }
];
  1. 第二步,在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;所以,前端结合接口文档进行数据的模拟就显得非常重要了;这样既可以让前端和后端团队可以在各自的工作上进行平行开发,减少了等待后端完成开发和部署的时间,提高了整体的开发效率,又可以帮助前端开发人员创建各种场景(如错误响应、延迟等)来测试前端的处理逻辑,确保应用在不同情况下的稳定性和可靠性;

具体实现的思路如下:

  1. 安装 mockjsvite-plugin-mock-dev-server 插件
yarn add mockjs, vite-plugin-mock-dev-server -D
  1. 配置 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: ''
        }
      }
    }
  })
})
  1. 编写接口请求

假如我们模拟一个数据列表请求,这里没什么需要解释的,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
      })
    }
  }
]);
  1. 模拟请求

接下来就是和正常请求接口一样,实现接口请求的逻辑;

这里我们使用了封装的axios自定义,后面会说到

import Http from '../utils/http/index';

export const getLists = () => {
  return Http.request({
    url: `/list/get`,
    method: 'get'
  });
};

这样,我们就可以正常的进行数据模拟了,是不是很Easy;主要是各位前端开发小伙伴要有这种意识,提高编码的质量和效率,剩下的时间就可以提升自己(摸鱼)了。

封装Axios请求库

这个就比较复杂了,就需要考量系统性、细节性的编码逻辑,我也是抽了一晚上的烟,才整体的撸出来,不过是通用的,每个项目中都可以直接cv,我只是比较懒,没有封装为npm包文件;

下面我也只是讲下思路,具体的代码各位知友可以去看代码仓库;

  1. 第一步,首先我们是基于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的效果,但是现在的用户哈,对视觉比较挑剔,需要一些个性化的效果来满足;

  1. 首先,定义一个自定义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整体遮罩的问题,同时避免样式污染;

  1. 下一步,实现一段自定义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);
  }
}
  1. 下一步就通过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图片的动态加载;我来详细的记录下,也是作为自己的一个方案解决记录;

  1. 当然第一步还是安装插件 vite-plugin-svg-icons
yarn add vite-plugin-svg-icons -D
  1. 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')]
      })
     ]
    })
 })
  1. 就是封装一个 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>
  1. 还有一步不要忘记了,在main.ts文件中,添加一段配置,也是插件要求的;

把svg注册

import 'virtual:svg-icons-register';
  1. 如果需要把组件注册为全局组件,就使用vue组件注册的方式;
const app = createApp(App);
app.component('i-svg-icon', ISvgIcon);

这样,就可以全局使用i-svg-icon的全局组件了;

其实方案很简单,只是会有一些细节的问题点需要知友注意的。

好啦,各位知友,今天的分享就到这里了;后期我也会不断地完善更新。

赠人玫瑰,手有余香 各位知友如果有更好的想法,欢迎各位知友留言呀,我期待和各位知友互动讨论👨‍💻👨‍💻‍👨‍💻‍👨‍💻‍👨‍💻‍👨‍💻‍,接收大家的指导和鼓励👏👏👏👏👏

转载自:https://juejin.cn/post/7424334647570169908
评论
请登录