创建vue3项目

539 阅读6分钟

官方文档

vitejs.cn/

这个项目我们尽量用yarn命令。

# 1、创建项目的命令

//npm 
npm init @vitejs/app 

//yarn 
yarn create @vitejs/app 

// 创建项目示例 npm init @vitejs/app [project-name] 
yarn create @vitejs/app [project-name] 

//下载node_modules 
yarn

# 2、选择创建vue项目

image.png

# 3、选择你想要的版本

image.png

4、回车后项目下载完成

image.png

5、安装node_nodules

yarn

6、安装我们开发需要的依赖,执行一下命令

//下载依赖 
//开发依赖 
yarn add vue-router@next vuex@next element-plus axios -S 
//生产依赖 yarn add sass -D 
//删除依赖(只是展示一下命令,并不需要去操作) 
yarn remove vue-router@next vuex@next element-plus axios 
yarn remove sass

7、我们的package.json文件来判断安装是否成功

{ 
"name": "vuevite",
"version": "0.0.0", 
"scripts": { 
"dev": "vite", 
"build": "vite build", 
"serve": "vite preview" 
},
"dependencies": { 
"axios": "^0.21.1", 
"element-plus": "^1.0.2-beta.70", 
"vue": "^3.0.5", 
"vue-router": "^4.0.11", 
"vuex": "^4.0.2" },
"devDependencies": { "@vitejs/plugin-vue": "^1.3.0",
"@vue/compiler-sfc": "^3.0.5",
"sass": "^1.37.5", 
"vite": "^2.4.4" 
}
}

8、注意事项

vite.config.js 用来配置项目的参数,类似vue.config.js
index.html

image.png

// type="module"让浏览器能识别ES6语法 
<script type="module" src="/src/main.js"></script>

2、配置项目所需架构

1、目录结构

vite.config.js作用和vue.config.js作用相似,但是配置的方式会有所不同,请仔细查看api文档进行配置

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
const path = require('path')

// https://vitejs.dev/config/
export default defineConfig({
	//修改服务的host和port
  server:{
    host:'127.0.0.1',
    port:'8080'
  },
//转换./src文件夹为@符号
  resolve: {
    alias:{
      '@': path.resolve( __dirname, './src' )
    }
  },
  plugins: [vue()]
})

vite获取环境变量的方式和在普通vue项目中不同

vue项目中获取环境变量process.env

import { createApp } from 'vue'
import App from './App.vue'

console.log("环境变量=>",import.meta.env);
createApp(App).mount('#app')

修改项目启动的模式package.json

 "scripts": {
    "dev": "vite --mode development",
    "build": "vite build",
    "serve": "vite preview"
  },

修改的规则

.env                # 所有情况下都会加载
.env.local          # 所有情况下都会加载,但会被 git 忽略
.env.[mode]         # 只在指定模式下加载
.env.[mode].local   # 只在指定模式下加载,但会被 git 忽略

现在我在之前配置了--mode development

项目启动后就会默认加载.env.development

VITE_BASE_URL=/api

为了防止意外地将一些环境变量泄漏到客户端,只有以 VITE_ 为前缀的变量才会暴露给经过 vite 处理的代码。

可以通过import.meta.env.VITE_SOME_KEY获取到定义过的这个变量。

举例:

main.js

import { createApp } from 'vue'
import App from './App.vue'

console.log("环境变量=>",import.meta.env);
createApp(App).mount('#app')

2、如何使用scss文件初始化样式

暂时不用深究语法

可以自行删减

./css/global.css

@charset "utf-8";

// 变量存储
// 字体Unicode编码 微软雅黑:\5FAE\8F6F\96C5\9ED1 , 宋体:\5B8B\4F53
$pcFont: '\5FAE\8F6F\96C5\9ED1', '\5B8B\4F53', arial;
$defaultColor: #333;
$mobileFont: 'Helvetica Neue', Helvetica, STHeiTi, Microsoft YaHei, sans-serif, Microsoft JhengHei, Arial;
$browser: null;

%display {
    display:inline-block;
    *display:inline;
    *zoom:1;
}
%text-indent {
    font-size:0;
    text-indent:-99999em;
    overflow:hidden;
}
%box-sizing {
    -webkit-box-sizing:border-box;
    -moz-box-sizing:border-box;
    -o-box-sizing:border-box;
    box-sizing:border-box;
}
// 绝对居中
@mixin center($width, $height) {
    position: absolute;
    left:50%;
    top:50%;
    width:$width;
    height:$height;
    margin:(-$height / 2) 0 0 (-$width / 2);
}
// 设置动画名称
@mixin animation($aniName) {
    -webkit-animation:$aniName;
    -moz-animation:$aniName;
    -o-animation:$aniName;
    animation:$aniName;
}
// 设置延迟执行时间
@mixin animation-delay($time) {
    -webkit-animation-delay:$time;
    -moz-animation-delay:$time;
    -o-animation-delay:$time;
    animation-delay:$time;
}
// 设置阴影
@mixin box-shadow($shadow...) {
    -webkit-box-shadow:$shadow;
    -moz-box-shadow:$shadow;
    -o-box-shadow:$shadow;
    box-shadow:$shadow;
}
// 圆角
@mixin border-radius($radius) {
    -webkit-border-radius:$radius;
    -moz-border-radius:$radius;
    -o-border-radius:$radius;
    border-radius:$radius;
}
// 设置过渡
@mixin transition($transition...) {
    -webkit-transition:$transition;
    -moz-transition:$transition;
    -o-transition:$transition;
    transition:$transition;
}
// 设置旋转位置
@mixin transform-origin($origin...) {
    -webkit-transform-origin:$origin;
    -moz-transform-origin:$origin;
    -o-transform-origin:$origin;
    transform-origin:$origin;
}
@mixin transform($transform...) {
    -webkit-transform:$transform;
    -moz-transform:$transform;
    -o-transform:$transform;
    transform:$transform;
}

// 设置关键帧
@mixin keyframes($name) {
    @-webkit-keyframes #{$name} {
        $browser: '-webkit-'; @content;
    }
    @-moz-keyframes #{$name} {
        $browser: '-moz-'; @content;
    }
    @-o-keyframes #{$name} {
        $browser: '-o-'; @content;
    }
    @keyframes #{$name} {
        $browser: ''; @content;
    }
}


/* ********************重置样式 reset******************** */

/* *********PC端********** */
body, dl, dd, h1, h2, h3, h4, h5, h6, p, form, figure, figcaption {
    margin:0px;
}
ul, ol {
    list-style:none;
    margin:0px;
    padding:0px;
}
body {
    font:14px/1.5 $pcFont;
    width:100%;
    color: $defaultColor;
    overflow-x:hidden;
}
h1,h2,h3,h4,h5,h6 {
    font-weight:normal;
}
/* 清除点击出现虚拟框 */
a{
    outline:none;
    text-decoration:none;
    -webkit-tap-highlight-color:rgba(0,0,0,0);
    &:focus{
        outline:0;
    }
    &:link, 
    &:visited {
        color: $defaultColor;
        text-decoration:none;
    }
}
a img {
    border:none;
}
input, textarea, select {
    outline:none;
    font:12px/1.5 $pcFont;
}

/* 清除浮动 */
.clearfix {
    *zoom:1;
    &:after {
        display:block;
        content:"\200B";
        clear:both;
        height:0;
    }
}


/* *********移动端********** */
body, dl, dd, h1, h2, h3, h4, h5, h6, p, form, figure, figcaption {
    margin:0px;
}
/* 改变盒子模型 */
section, article, nav, aside, footer, header, div, p, ul, li, input, textarea {
    display: block;
    @extend %box-sizing;
}
html, body {
    -webkit-user-select: none;
    /* 禁止选中文本 */
    user-select: none;
    -webkit-text-size-adjust: 100%;
    /* iphone禁用文字大小调整 */
    -ms-text-size-adjust: 100%;
}
html {
    font-size:625%;
}
body{
    font:.16rem/1.6 $mobileFont; 
    color:#333;
    -webkit-overflow-scrolling: touch;
}
h1, h2, h3, h4, h5, h6{
    font-weight:normal;
}
/* 清除点击虚拟框 */
a, div, p, span, ul, li, i, img, input {
    outline:0;
    text-decoration:none;
    -webkit-tap-highlight-color:rgba(0,0,0,0);
}
a:focus{
    outline:0;
}
a:link,a:visited{
    color:$defaultColor;
    text-decoration:none;
}
a img{
    border:0 none;
}
a, img {
    -webkit-touch-callout: none;
    /* 禁止长按链接与图片弹出菜单 */
}
input, textarea, select {
    outline: none;
    color: $defaultColor;
    font-family: $mobileFont;
}
input {
    /* 清除 iphone 中 input 默认样式 */
    -webkit-appearance: none;
}
/* 清除浮动 */
.clearfix {
    *zoom:1;
    &:after {
        display:block;
        content:"\200B";
        clear:both;
        height:0;
    }
}

3、配置路由文件

vite的底层是rollup,所以没有这个chunk

import { createRouter, createWebHashHistory } from 'vue-router';
import Login from '../views/Login.vue';

const routes = [
  {
    path:'/',
    name:'login',
    component:Login
  },
  {
    path:'/home',
    name:'home',
    component:()=>import('../views/Home.vue')
  }
]


const router = createRouter({
  history:createWebHashHistory(),
  routes
})

export default router;

4、如何配置axios

./config/index.js

/**
 * 统一的环境变量配置
 */

 const env = import.meta.env.MODE || 'production';
 const EnvConfig = {
   //开发环境
   development:{
     baseApi:'/api',
     mockApi:'https://www.fastmock.site/mock/eab0ffc31201e85b4aa3810df5e28237/api'
   },
   test:{
     baseApi:'/',
     mockApi:'/'
   },
   //线上环境
   production:{
     baseApi:'www.babdfjkdh.com/api' // 假的地址,举例用的
   }
 }
 
 export default {
   env,
   mock:true,
   namespace:'punch', // 用于sessionStroage的封装
   ...EnvConfig[env]
 }

简单版./utils/request.js

import axios from 'axios'
import config from './../config'


// 创建axios实例对象,添加全局配置
const _axios = axios.create({
    baseURL: config.baseApi,
    timeout: 10000
})

// 添加请求拦截器
_axios.interceptors.request.use((config)=>{
    // 在发送请求之前做些什么
    return config;
  },(error)=>{
    // 对请求错误做些什么
    return Promise.reject(error);
  });
  

// 添加响应拦截器
_axios.interceptors.response.use((res)=> {
    // 对响应数据做点什么
    const { code, data } = res.data;
    if (code === 200) {
        return data;
    } 
    return res;
  },(error)=>{
    // 对响应错误做点什么
    return Promise.reject(error);
});
  
/**
 * 请求核心函数,mock数据切换的逻辑
 * @param {*} options 请求配置
 */
 function request(options) {
    //兼容。没有传HTTP方法的时候,默认使用get方法
    options.method = options.method || 'get'
    //因为axiosget传参要使用param(固定写法),所以需要转换一下
    if (options.method.toLowerCase() === 'get') {
        options.params = options.data;
    }
  
    //在调用接口的时候有没有传mock这个参数
    let isMock = config.mock;
  
    //线上环境必须使用后台接口的路径的一个安全措施
    if (config.env === 'production') {
        _axios.defaults.baseURL = config.baseApi
    } else {
        //如果不是线上"production"线上环境
        //就判断我们传的mock是true还是false来做一个所有mock数据的开关
        _axios.defaults.baseURL = isMock ? config.mockApi : config.baseApi
    }
  
    return _axios(options)
  }
  
  export default request;

./utils/request.js

import axios from 'axios'
import config from './../config'
import router from '../router'
import { ElMessage } from 'element-plus'

const TOKEN_INVALID = 'Token认证失败,请重新登录'
const NETWORK_ERROR = '网络请求异常,请稍后重试'

// 创建axios实例对象,添加全局配置
const service = axios.create({
    baseURL: config.baseApi,
    timeout: 10000
})

// 请求拦截
service.interceptors.request.use((req) => {
  const headers = req.headers;
  if (!headers.Authorization) headers.Authorization = 'Bearer ';
  return req;
})

// 响应拦截
service.interceptors.response.use((res) => {
  const { code, data, msg } = res.data;
  if (code === 200) {
      return data;
  } else if (code === 401) {
      ElMessage.error(TOKEN_INVALID)
      setTimeout(() => {
          router.push('/login')
      }, 1500)
      return Promise.reject(TOKEN_INVALID)
  } else {
      ElMessage.error(msg || NETWORK_ERROR)
      return Promise.reject(msg || NETWORK_ERROR)
  }
})

/**
 * 请求核心函数
 * @param {*} options 请求配置
 */
 function request(options) {
  //兼容。没有传HTTP方法的时候,默认使用get方法
  options.method = options.method || 'get'
  //因为axiosget传参要使用param(固定写法),所以需要转换一下
  if (options.method.toLowerCase() === 'get') {
      options.params = options.data;
  }

  //在调用接口的时候有没有传mock这个参数
  let isMock = config.mock;

  //线上环境必须使用后台接口的路径的一个安全措施
  if (config.env === 'production') {
      service.defaults.baseURL = config.baseApi
  } else {
      //如果不是线上"production"线上环境
      //就判断我们传的mock是true还是false来做一个所有mock数据的开关
      service.defaults.baseURL = isMock ? config.mockApi : config.baseApi
  }

  return service(options)
}

export default request;

./api/index.js

import request from '../utils/request.js';

export const login = (params) =>{
  return request({
    url: '/users/login',
    method: 'post',
    data:params
  })
}

在页面上调用的时候可以引入这个login来使用


5、注册全局可用的方法

./main.js

举例如下,请直接看如下代码第12行

import { createApp } from 'vue'
import ElementPlus from 'element-plus';
import router from './router'
import 'element-plus/lib/theme-chalk/index.css';
import App from './App.vue'
import storage from './utils/storage';

const app = createApp(App)
//全局注册
app.config.globalProperties.$storage = storage

console.log("环境变量=>",import.meta.env);
app.use(ElementPlus).use(router).mount('#app')

./utils/storage

/**
 * localstorage封装
 * @author 谭威
 */
import config from '../config'
//config.namespace 为punch自定义
export default {
  //设置一个值
  setItem(key,val){
    let storage = this.getStorage();
    storage[key] = val;
    window.localStorage.setItem(config.namespace,JSON.stringify(storage))
  },
  //获取一个值
  getItem(key){
    return this.getStorage()[key]
  },
  //从localStorag中取出数据
  getStorage(){
    return JSON.parse(window.localStorage.getItem(config.namespace) || '{}')
  },
  //清除一个值
  cleanItem(key){
    let storage = this.getStorage();
    delete storage[key];
    window.localStorage.setItem(config.namespace,JSON.stringify(storage))
  },
  //清除所有
  cleanAll(){
    window.localStorage.clear()
  }
}

如何去页面调用呢?这里讲的是在setup中调用的方法

import { getCurrentInstance, onMounted } from 'vue'

const { proxy, ctx } = getCurrentInstance()
onMounted(()=>{
  //注册
  proxy.storage.setItem('age','18');
  //清空所有
  proxy.$storage.cleanAll();
})