移动端vue3项目构建流程(傻瓜式,按照步骤即可)

341 阅读8分钟

项目描述

移动端项目,除登陆注册等少数页面外,大多数页面有公共的头部,部门页面有公共底部项目。 去年下半年,vue刚刚推出,可选UI框架都比较少。项目组多人开发,项目并非本人搭建。昨天刚好开启一个新的项目,按照各种教程完成基础的搭建,很少找到一次性说完项目搭建方法的教程。搭建过程中可能还有错的地方,写出搭建过程,一来可以留份文档,以后搭建可以使用,二来可以请大家及时指出错误的地方,共同学习。


技术栈

vue3 + vite + js + Vant + axios + pinia + scss + rem

注: 选择ts的技术栈大致相同,部分项目配置函数和函数调用略有不同,请参考网络博客教程(后续有时间会补上)。

项目搭建步骤

一、项目创建

1.vite安装

npm install -g vite

安装结束,查看vite的版本号正常,即安装完成。

2.项目初始化

npm init vite@latest my-vue-app -- --template vue
cd my-vue-app
npm install
npm run dev

安装选择js,完成以上四步安装后能够正常启动即完成项目初始化。

3.添加路由

npm install vue-router@4

1.添加完路由依赖后,在src文件夹里面创建一个router文件夹,并在router文件夹里面创建一个index.js文件。

// src/router/index.js 文件内容:

import { createRouter, createWebHashHistory } from 'vue-router'
export default createRouter({
    history:createWebHashHistory(),
    routes:[{
        path: "/",
        name: "Index",
        redirect: "/Home",
        component: ()=>import('@/components/Index.vue'),
        children: [{
            path:'/Home',
            name:"Home",
            component:()=>import('@/views/Home/index.vue')
        },{
            path:'/User',
            name:"User",
            component:()=>import('@/views/User/index.vue')
        }]
      }]
})

注: 路由中的.vue文件路径和实际文件路径要一致。

2.在main.js中添加路由

// main.js 文件内容:

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router/index'

const app = createApp(App)

app.use(router)
app.mount('#app')

3.app.vue

// app.vue 内容:

<template>
  <router-view v-slot="{ Component }">
    <transition name="el-fade-in" mode="out-in">
      <component :is="Component" />
    </transition>
  </router-view>
</template>
  1. src/components/Index.vue,加载一级路由,然后分发二级路由,实现公共头部。

    注: 使用slot插槽也可以实现此功能,并非只有一种方式。

// src/components/Index.vue 文件内容:

<template>
  <div class="project_container">
    <div class="header_container">
      <Header/>
    </div>
    <router-view></router-view>
  </div>
</template>
<script>
import { reactive, toRefs } from "vue";
import Header from "@/components/Header.vue";
export default {
  components:{Header},
  setup() {
    const state = reactive({});
    return {
      ...toRefs(state),
    };
  },
};
</script>
<style lang="scss" scoped>
.project_container{
  padding-top: 50px;
  background-color: #f3f4f5;
}
</style>

4.安装vant

可以通过vant官网教程安装。

npm i vant

// Vue 3 项目,安装最新版 Vant

1.全局引用,(一般很少局部引用,基本都是全局引用)main.js中配置

// main.js 文件内容:

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router/index'
import Vant from 'vant'	 // 引用组件
import 'vant/lib/index.css'	// 引用样式

const app = createApp(App)

app.use(router)
app.use(Vant) 	// 使用
app.mount('#app')

注: vite.config.js中不需要安装官网教程配置,vite已自动配置支持,如后续测试安装失败,则再按照官网教程配置。

完整的vite.config.js:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { loadEnv } from './src/assets/js/vite'

const viteEnv = loadEnv()
const { VITE_PORT } = viteEnv

import path from 'path'
export default defineConfig({
  plugins: [vue()],
  server: {
    port: VITE_PORT
  },
  resolve: {
    extensions: ['.vue', '.js', '.css'],
    alias: {
      '@': path.resolve(__dirname, './src')
    },
  },
  build: {
    outDir: 'dist',
    assetsDir: "assets", //指定静态资源存放路径
  }
})

2.在组件中使用vant,通过测试按钮是否显示来判断vant安装配置是否成功。

<template>
  <van-button type="primary" />
</template>

5.安装pinia

pinia是vuex的升级版本,比vuex简单好用。此处只写简单存储取值应用,复杂情况请参考官网文档。

npm install pinia -S

1.在mian.js中引用

// main.js 文件内容:

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router/index'
import Vant from 'vant'
import 'vant/lib/index.css'
import { createPinia } from 'pinia'	 // 引用组件

const app = createApp(App)

app.use(router)
app.use(createPinia()) 	// 使用
app.use(Vant)
app.mount('#app')

2.在src文件夹下面创建store文件夹,在store文件夹下创建index.js文件

import { defineStore } from "pinia"
 
export const userStore = defineStore({
    id: "mooc", // id是唯一的,如果有多个文件,ID不能重复
    state: () => {
        return {
            customList: [{
                id:1,
                name:'马云',
                phone:'13856784223',
                img:'https://img2.baidu.com/it/u=4244269751,4000533845&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
                sex:'男',
                creatTime:'2022-09-18',
                remark:"我不喜欢钱。"
            },{
                id:2,
                name:'王健林',
                phone:'13889657458',
                img:'https://img1.baidu.com/it/u=1016138010,1907110459&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
                sex:'男',
                creatTime:'2022-09-18',
                remark:"先赚他个小目标!"
            },{
                id:3,
                name:'董明珠',
                phone:'13885236548',
                img:'https://img2.baidu.com/it/u=3062813899,1142128231&fm=253&fmt=auto&app=138&f=JPEG?w=479&h=500',
                sex:'女',
                creatTime:'2022-09-18',
                remark:"我进来你们为什么不鼓掌?"
            },{
                id:4,
                name:'马化腾',
                phone:'13885468735',
                img:'https://img2.baidu.com/it/u=4244269751,4000533845&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
                sex:'男',
                creatTime:'2022-09-18',
                remark:"对不起,您的贞操碎了,一百万年后,您的贞操碎片将再次集齐!"
            }],
        }
    },
    actions: {
        setInfo(data) {
            this.customList = data
        },
        // 用户退出,清除本地数据
        logout() {
            this.customList = null
            sessionStorage.clear()
            localStorage.clear()
        },
    },
    // 开启数据缓存,在 strategies 里自定义 key 值,并将存放位置由 sessionStorage 改为 localStorage
    // 默认所有 state 都会进行缓存,你可以通过 paths 指定要持久化的字段,其他的则不会进行持久化,如:paths: ['userinfo'] 替换key的位置
    persist: {
        enabled: true,
        strategies: [
            {
                key: "moocs",
                storage: localStorage,
            },
        ],
    },
})

3.在组件中使用(测试一下是否能够正常读取数据)

// 引用
import { userStore } from "@/store/index";

// 使用
const store = userStore();
const customList = store.customList;// 即为存储数据,(此时为默认数据)
// 修改
const list = [1,2,3,4,5,6]
store.setInfo(list);

注: 使用方法有多种,请参考官网文档。此处仅为演示简单的使用。

6.添加rem配置

注: 实际项目本身自带rem,但是自带rem配置不好修改符合个人习惯的比例。介绍几种方法(方法很多,选择其中一个即可): 1.在index.html中直接添加

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="http://www.guoketest.com/uploads/20220818/0f63a4395ca47752ebbeccda90b0f5ce.png" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>vue3项目演示</title>
    <script>
      function htmlFontSize(){
          var docEl = document.documentElement,
              clientWidth = window.innerWidth || docEl.clientWidth || document.body.clientWidth;
          if(!clientWidth) return;
          docEl.style.fontSize = 100 * (clientWidth/1920) + 'px';
          if(!document.addEventListener) return;
      }
      htmlFontSize();
    </script>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>

2.在assets/js路径的文件夹中创建rem.js文件

const baseSize = 36
// 设置 rem 函数
function setRem () {
  // 当前页面宽度相对于 1920宽的缩放比例,可根据自己需要修改。
  const scale = document.documentElement.clientWidth / 1500
  // 设置页面根节点字体大小(“Math.min(scale, 2)” 指最高放大比例为2,可根据实际业务需求调整)
  document.documentElement.style.fontSize = baseSize * Math.min(scale, 1) + 'px'
}
// 初始化
setRem()
// 改变窗口大小时重新设置 rem
window.onresize = function () {
  setRem()
}


// 或者:


// 获取html元素
var html =  document.documentElement;
//获取屏幕的宽度
var screenWidth = html.clientWidth;
var timer = null;
// 初始的设计图的大小
var uiWidth = 320;
// 初始的font-size的大小
var fonts = 16;
// 获取初始的比例
var bili = uiWidth/fonts;
// 根据当前屏幕大小动态去计算这个屏幕所对应font-size值
html.style.fontSize = screenWidth/bili + 'px';

// 上来的时候先执行一次
getSize();

window.onresize = getSize;

function getSize(){
	clearTimeout(timer);
	timer = setTimeout(function(){
		// 重新得到屏幕的宽度
		screenWidth = html.clientWidth;
		// 针对屏幕宽度做限定
		if(screenWidth <= 320){
			html.style.fontSize = 320/bili + 'px';
		}
		else if(screenWidth >= 640){
			html.style.fontSize = 640/bili + 'px';
		}
		else{
			// 根据当前屏幕大小动态去计算这个屏幕所对应font-size值
			html.style.fontSize = screenWidth/bili + 'px';
		}	
	}, 100);
}
// 改变窗口大小时重新设置 rem
window.onresize = function () {
  getSize()
}


// 千万注意:不要添加入口函数
// 同时引用的时候放到最前面

在main.js中配置引用

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router/index'
import Vant from 'vant'
import 'vant/lib/index.css'
import './assets/js/rem'	// 引用样式
import { createPinia } from 'pinia'

const app = createApp(App)

app.use(router)
app.use(createPinia())
app.use(Vant)
app.mount('#app')

7.安装axios

npm install axios --save

1.在src目录下新建一个request文件夹,在里面新建index.js文件

import axios from 'axios'

// 创建一个 axios 实例
const service = axios.create({
	baseURL: '/api', // 所有的请求地址前缀部分
	timeout: 60000, // 请求超时时间毫秒
	withCredentials: true, // 异步请求携带cookie
	headers: {
		// 设置后端需要的传参类型
		'Content-Type': 'application/json',
		'token': 'your token',
		'X-Requested-With': 'XMLHttpRequest',
	},
})

// 添加请求拦截器
service.interceptors.request.use(
	function (config) {
		// 在发送请求之前处理
		return config
	},
	function (error) {
		// 对请求错误处理
		console.log(error)
		return Promise.reject(error)
	}
)

// 添加响应拦截器
service.interceptors.response.use(
	function (response) {
		console.log(response)
		// 2xx 范围内的状态码都会触发该函数。
		// 对响应数据做处理
		// dataAxios 是 axios 返回数据中的 data
		const dataAxios = response.data
		// 后端给出的状态码
		const code = dataAxios.reset
		return dataAxios
	},
	function (error) {
		// 超出 2xx 范围的状态码都会触发该函数。
		// 对响应错误做处理
		console.log(error)
		return Promise.reject(error)
	}
)

export default service

2.在src目录下新建一个api文件夹,在api下面创建user.js文件

// 导入axios实例
import httpRequest from '@/request/index'

// 获取用户信息
export function getUserInfo(param) {
    return httpRequest({
		url: 'your api url',
		method: 'post',
		data: param,
	})
}

3.具体使用

import { reactive, toRefs } from "vue";
import { getUserInfo } from '@/api/user'

export default {
  setup() {
      
    const state = reactive({});
      
    getUserInfo({id:******,userName:******})
      .then((res) => {
      console.log(res)
    })
      
    return {
      ...toRefs(state),
    };
  },
};

若是没算错的话,此时你应该有个跨域错误。这个跨域与之前项目处理一样,实在不会处理的话就甩给后端处理。

二、项目打包

npm run build

打包后得到一个文件夹,文件夹里包含一个index.html,以及css和js各一个文件。将文件夹内容放到nginx服务器中,即完成上传。

建议使用Jenkins自动打包发布工具,可视化,一键完成项目打包、打包文件上传、重启前端服务器等全部工作。

三、开发注意事项

js写法

vue3 js有2中写法,如果使用ts的话,建议使用第二种。

第一种写法是vue3.2版本推出,在引入组件时会有缺少export问题,在ts的组件引用会抛错。

第二种写法是vue3.1版本推出,实践中未遇到问题。

<!-- 第一种写法: -->

<script setup>
    import { reactive, toRefs } from "vue";
    const state = reactive({
        token: ''
     });
    const { token } = toRefs(state);
</script>

<!-- 此方法在部分情景引用状态会报错(已遇到坑,修改使用方法即解决),大部分场景无问题,使用时需要注意。 -->
// 第二种写法:

import { reactive, toRefs } from "vue";

export default {
  setup() {
      
    const state = reactive({}); 
      
    return {
      ...toRefs(state)
    };
  },
};

// 此方法中规中矩,适合所有场景。
//部分骚操作需要多写几行代码才能实现(具体问题具体对待,遇到问题自然就知道怎么写了)。

函数定义

函数定义:setup中使用少使用箭头函数,减少不必要的麻烦,如:先定义再调用等。

setup() {

    const state = reactive({
        id:null,
        status:null
    }); 
    
    changeId(1) // 可以调用
    changeStatus(1) // 不可以调用
    
    //推荐使用
    function changeId(id){
        state.id = id
    }
    
    //不推荐使用
    const changeStatus = status => {
        state.status = status
    }
    
    changeId(2) // 可以调用
    changeStatus(2) // 可以调用
    
    return {
      ...toRefs(state),
      changeStatus,
      changeId
    };
    
}

组件的引用

组件的引用写法:建议使用大驼峰命名法。

import EditDialog from "./components/editDialog.vue";
<!-- 推荐使用 -->

<EditDialog />

<!-- 大驼峰比短横线写法更方便定位到使用地方,更方便多人开发维护 -->
<!-- 不推荐使用 -->

<edit-dialog />

<!-- 此方法不容易搜索,对不是自己开发的代码维护不友好。 -->

以上建议可以不用遵守,纯属于个人代码习惯。


有谬误地方,请各位大佬斧正。