vue2项目模板:新建一个vue2项目并加入基础建设

1.前置环境准备:

node环境:下载并安装node
vue-cli的安装:npm i vue-cli -g

2.使用vue create 项目名生成一个vue2项目,具体配置如下:

image.png
根据以上的配置,你会得到一个vue + vue-router + vuex + less + eslint的vue2项目模板

3.加入UI组件库,以element-ui为例子

安装命令:

npm i element-ui -S

如果安装不上,是因为npm版本问题报错**,**则使用

npm i element-ui -S --legacy-peer-deps

image.png
打开项目的main.js,添加以下代码(全部引入,需要优化为按需导入请看element官网)即可

import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(ElementUI)

image.png
在页面中使用element的组件没有问题则引入成功

4.建立一些必须要用到的文件夹

image.png

5.消灭浏览器差异以及盒子原有样式

(1)normalize.css保证我们的代码在各浏览器的一致性

  1. 安装 npm install --save normalize.css(报错的话,跟上面一样加上–legacy-peer-deps)
  2. main.js引入 import ‘normalize.css/normalize.css’
    (2)消除盒子原有样式(简化版)
    1.建立rest.css,写入以下样式:
* {
    
    
  padding: 0;
  margin: 0;
  list-style: none;
  box-sizing: border-box;
}
html,
body {
    
    
  height: 100%;
}

2.在main.js中引入:import ‘./assets/css/reset.css’
image.png
image.png

6.配置别名

在vue.config.js中添加:

const path = require('path')
function resolve(dir) {
    
     // 配置别名
	return path.join(__dirname, dir)
}
const {
    
     defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
    
    
  transpileDependencies: true,
  lintOnSave: false,
  configureWebpack: {
    
    
		resolve: {
    
    
			alias: {
    
    
				'@': resolve('src')
			}
		}
	},
})

image.png

7.配置代理

在vue.config.js中添加:

// 样例
  devServer: {
    
    
    //开启代理服务器 (方式1)   配置多个代理
    proxy: {
    
    
      "/api": {
    
    
        //'/api'是自行设置的请求前缀
        target: "http://localhost:5000",
        // ws: true,//用于支持websocket
        changeOrigin: true, //用于控制请求头中的host值
        pathRewrite: {
    
     "^/api": "" }, //路径重写,(正则)匹配以api开头的路径为空(将请求前缀删除)
      },
    },
  },

image.png

8.vuex的store改造

因为在项目中,一般是分仓库进行存储数据,所以我们需要对store进行修改一下,把它改成分仓库存储数据的模式。这里可以参考vue-element-admin的vuex仓库
创建这几个文件
image.png
index.js:

import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'

Vue.use(Vuex)

// https://webpack.js.org/guides/dependency-management/#requirecontext
const modulesFiles = require.context('./modules', true, /\.js$/)

// you do not need `import app from './modules/app'`
// it will auto require all vuex module from modules file
const modules = modulesFiles.keys().reduce((modules, modulePath) => {
    
    
  // set './app.js' => 'app'
  const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
  const value = modulesFiles(modulePath)
  modules[moduleName] = value.default
  return modules
}, {
    
    })

const store = new Vuex.Store({
    
    
  modules,
  getters
})

export default store

getters.js(示例):

const getters = {
    
    
  token: state => state.user.token,
  name: state => state.user.username,
}
export default getters

user.js中(示例):

const state = {
    
    
  token: '',
  username: '',
  avatar:'',
  desc:''
}
const mutations = {
    
    
  setName(state, data){
    
    
    state.username = data
  },
  saveUserInfo(state, data) {
    
    
    state.token = data.token
    state.username = data.username
    state.avatar = data.avatar
    state.desc = data.desc
  },
  removeUserInfo(state) {
    
    
    state.token = ''
    state.username = ''
    state.avatar = ''
    state.desc = ''
  },
}
export default {
    
    
  namespaced: true,
  state,
  mutations,
}

使用示例:

import {
    
     mapState,mapMutations } from 'vuex';
  computed: {
    
    
    ...mapState('user', {
    
    
      username: 'username'
    })
  },
created () {
    
    
   console.log(this.username)
    console.log(this.$store.state.user.username);
    this.setName('李四')
    this.generateRoutes()
    this.$store.dispatch('permission/generateRoutes')
  },
  methods: {
    
    
    ...mapMutations('user', {
    
    
      setName: 'setName'
    }),
       ...mapActions('permission', {
    
    
      generateRoutes: 'generateRoutes'
    })
  },

image.png
加入持久化插件:vuex-persistedstate
安装插件:

npm i vuex-persistedstate 或者 npm i vuex-persistedstate --legacy-peer-deps

在store中使用(这里是把user仓库的数据存储到了sessionStorage中):

import createPersistedState from 'vuex-persistedstate'
const store = new Vuex.Store({
    
    
  modules,
  getters,
  plugins: [
    createPersistedState({
    
    
      storage: window.sessionStorage,
      reducer(val) {
    
    
        return {
    
    
          User: val.user,
        }
      },
    }),
  ],
})

9.添加axios并封装axios

安装axios:

npm i axios

在utils文件夹下创建auth.js,加入以下代码:

function getLocal(key = "token") {
    
    
  return localStorage.getItem(key);
}
// 删除
function removeLocal(key = "token") {
    
    
  window.localStorage.removeItem(key);
}
// 保存
function setLocal(value, key = "token") {
    
    
  window.localStorage.setItem(key, value);
}
export {
    
     getLocal, removeLocal, setLocal };

在utils文件夹下创建request.js,加入以下代码:
详细的封装过程请移步这篇博客(这个代码是通用的,可直接copy):
https://blog.csdn.net/weixin_43239880/article/details/130143352?spm=1001.2014.3001.5501

import axios from 'axios'
import {
    
     Message, Loading, MessageBox } from 'element-ui'
import store from '@/store'
import {
    
     getLocal } from '@/utils/auth'

// 创建axios实例
const service = axios.create({
    
    
  baseURL: process.env.VUE_APP_URL, // api的base_url
  timeout: 5000 // 请求超时时间
})

// request拦截器
service.interceptors.request.use(
  config => {
    
    
    removePending(config)
    // 如果repeatRequest不配置,那么默认该请求就取消重复接口请求
    !config.repeatRequest && addPending(config)
    // 打开loading
    if (config.loading) {
    
    
      LoadingInstance._count++
      if (LoadingInstance._count === 1) {
    
    
        openLoading(config.loadingDom)
      }
    }
    // 如果登录了,有token,则请求携带token
    // Do something before request is sent
    if (store.state.user.token) {
    
    
      config.headers['X-Token'] = getLocal('token') // 让每个请求携带token--['X-Token']为自定义key 请根据实际情况自行修改
    }
    return config
  },
  error => {
    
    
    // Do something with request error
    console.log(error) // for debug
    Promise.reject(error)
  }
)

// respone拦截器
service.interceptors.response.use(
  // response => response,
  /**
   * 下面的注释为通过response自定义code来标示请求状态,当code返回如下情况为权限有问题,登出并返回到登录页
   * 如通过xmlhttprequest 状态码标识 逻辑可写在下面error中
   */
  response => {
    
    
    // 已完成请求的删除请求中数组
    removePending(response.config)
    // 关闭loading
    if (response.config.loading) {
    
    
      closeLoading()
    }

    const res = response.data
    // 处理异常的情况
    if (res.code !== 200) {
    
    
      Message({
    
    
        message: res.message,
        type: 'error',
        duration: 5 * 1000
      })
      // 403:非法的token; 50012:其他客户端登录了;  401:Token 过期了;
      if (res.code === 403 || res.code === 50012 || res.code === 401) {
    
    
        MessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录', '确定登出', {
    
    
          confirmButtonText: '重新登录',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
    
    
          store.dispatch('user/FedLogOut').then(() => {
    
    
            location.reload() // 为了重新实例化vue-router对象 避免bug
          })
        })
      }
      return Promise.reject(new Error('error'))
    } else {
    
    
      // 默认只返回data,不返回状态码和message
      // 通过 meta 中的 responseAll 配置来取决后台是否返回所有数据(包括状态码,message和data)
      const isbackAll = response.config.meta && response.config.meta.responseAll
      if (isbackAll) {
    
    
        return res
      } else {
    
    
        return res.data
      }
    }
  },
  error => {
    
    
    error.config && removePending(error.config)
    // 关闭loading
    if (error.config.loading) {
    
    
      closeLoading()
    }
    console.log('err' + error) // for debug
    Message({
    
    
      message: error.message,
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)

// --------------------------------取消接口重复请求的函数-----------------------------------
// axios.js
const pendingMap = new Map()
/**
 * 生成每个请求唯一的键
 * @param {*} config
 * @returns string
 */
function getPendingKey (config) {
    
    
  let {
    
     url, method, params, data } = config
  if (typeof data === 'string') data = JSON.parse(data) // response里面返回的config.data是个字符串对象
  return [url, method, JSON.stringify(params), JSON.stringify(data)].join('&')
}

/**
 * 储存每个请求唯一值, 也就是cancel()方法, 用于取消请求
 * @param {*} config
 */
function addPending (config) {
    
    
  const pendingKey = getPendingKey(config)
  config.cancelToken =
    config.cancelToken ||
    new axios.CancelToken(cancel => {
    
    
      if (!pendingMap.has(pendingKey)) {
    
    
        pendingMap.set(pendingKey, cancel)
      }
    })
}
/**
 * 删除重复的请求
 * @param {*} config
 */
function removePending (config) {
    
    
  const pendingKey = getPendingKey(config)
  if (pendingMap.has(pendingKey)) {
    
    
    const cancelToken = pendingMap.get(pendingKey)
    cancelToken(pendingKey)
    pendingMap.delete(pendingKey)
  }
}
// ----------------------------------loading的函数-------------------------------
const LoadingInstance = {
    
    
  _target: null, // 保存Loading实例
  _count: 0
}
function openLoading (loadingDom) {
    
    
  LoadingInstance._target = Loading.service({
    
    
    lock: true,
    text: '数据正在加载中',
    spinner: 'el-icon-loading',
    background: 'rgba(25, 32, 53, 1)',
    target: loadingDom || 'body'
  })
}
function closeLoading () {
    
    
  if (LoadingInstance._count > 0) LoadingInstance._count--
  if (LoadingInstance._count === 0) {
    
    
    LoadingInstance._target.close()
    LoadingInstance._target = null
  }
}

export default service

axios的使用:在api文件夹中建立home.js,加入以下代码(示例):

import request from "@/utils/request";

//使用封装好的request
export function getInfo(params) {
    
    
  return request({
    
    
    url: "/user/info",
    method: "get",
    params,
  });
}

//返回所有的res信息
export function login(data) {
    
    
  return request({
    
    
    url: "/user/login",
    method: "post",
    data,
    meta: {
    
    
      responseAll: true, // 返回所有的信息,包括状态码和message和data
    },
  });
}

//使用repeatRequest参数让接口 可以同一时间多次调用
export function getList(params) {
    
    
  return request({
    
    
    url: "/message/list",
    method: "get",
    params,
    repeatRequest: true, // 配置为true,则可以同一时间多次调用
  });
}

//使用loading
export function getListById(loading) {
    
    
  return request({
    
    
    url: "/message/listbyId",
    method: "get",
    ...loading,
  });
}

export function gettableList() {
    
    
  return request({
    
    
    url: "/user/tableData",
    method: "get",
  });
}

10.区分生产环境和开发环境

在src目录建立两个文件夹(名字固定),定义变量VUE_APP_URL
image.png
如何使用该变量?使用process.env.VUE_APP_URL来调用
image.png

11.封装路由拦截器

router文件夹的index.js中(示例可用)

import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '@/views/HomeView.vue'

Vue.use(VueRouter)

// 解决重复点击路由报错的BUG
// 下面这段代码主要解决这个问题 :Uncaught (in promise) Error: Redirected when going from "/login" to "/index" via a navigation guard.
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
    
    
  return originalPush.call(this, location).catch(err => err)
}

const routes = [
  {
    
    
    path: '/',
    name: 'home',
    component: HomeView
  },
  {
    
    
    path: '/about',
    name: 'about',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '@/views/AboutView.vue')
  }
]

const router = new VueRouter({
    
    
  mode: 'history',
  base: process.env.VUE_APP_URL,
  routes
})

export default router

在router文件夹下创建router-config.js,并在main.js中导入:

import "./router/router-config"  // 路由守卫,做动态路由的地方

router-config.js中的代码: 由于封装路由拦截器需要和项目选中前端路由还是后端路由有关系,所以这里就简单的写了样例。

import router from './index'

// 白名单页面直接进入
const whiteList = ['/login']
console.log(whiteList)

router.beforeEach((to, from, next) => {
    
    
  next()
})

router.afterEach(() => {
    
    
  // finish progress bar
  //   NProgress.done()
})

需要使用前端路由的,可参考我的这篇文章:
https://blog.csdn.net/weixin_43239880/article/details/129943035?spm=1001.2014.3001.5501
需要使用后端路由的,可参考我的这篇文章:
https://blog.csdn.net/weixin_43239880/article/details/129922664?spm=1001.2014.3001.5501
其中主要的逻辑为:
是否为白名单页面
    是:直接进入
    不是:判断是否有token
        无token:重置到login页面
        有token: 判断用户的路由状态(指的是用户是否有路由)
            有路由:直接进入
            没有路由:调接口去获取路由
                              递归处理后台返回的路由
                              将异步路由存储到vuex,并设置用户的路由状态为true
                              使用route.addRouters将路由添加进去

12.适配处理

适配处理参考这篇文章https://blog.csdn.net/weixin_43239880/article/details/130135971?spm=1001.2014.3001.5501,因为不知道是什么项目,所以这里就不加进去了。

各类需求适合适配的主方案:

移动端: rem(lib-flexible+postcss-pxtorem) 或者 vw方案 参考vant官方文档:https://vant-ui.github.io/vant/#/zh-CN/advanced-usage#liu-lan-qi-gua-pei
PC大屏: scale优先
PC端(官网主页/购物网站/类似知乎,淘宝,小米等主页):使用版心布局
PC端(有地图或不允许留白(设计稿完全占满了1920*1080)):rem(lib-flexible+postcss-pxtorem)+其他响应式方法

10.配置prettier(可选)

1.安装必须的包

  • prettier - prettier 本体
  • eslint-config-prettier - 关闭 ESLint 中与 Prettier 中发生冲突的规则
  • eslint-plugin-prettier - 将 Prettier 的规则设置到 ESLint 的规则中
npm i prettier eslint-config-prettier@6 eslint-plugin-prettier@3 -D

安装时的报错解决:指定某个大版本就ok了,这个报错很明显是因为eslint和eslint-plugin-prettier的版本搭配不正确导致的
image.png
2.在根目录添加一个 .prettierrc.js 文件,内容如下:

module.exports = {
    
    
  tabWidth: 2, // 指定每个缩进级别的空格数<int>,默认2
  useTabs: false, // 用制表符而不是空格缩进行<bool>,默认false
  printWidth: 300, //一行的字符数,如果超过会进行换行,默认为80
  singleQuote: true, //字符串是否使用单引号,默认为false,使用双引号
  endOfLine: 'auto', //避免报错delete (cr)的错
  proseWrap: 'always',
  semi: false, // 不加分号
  trailingComma: 'none', // 结尾处不加逗号
  htmlWhitespaceSensitivity: 'ignore', // 忽略'>'下落问题
}

3.修改 ESLint 配置,使 Eslint 兼容 Prettier 规则
plugin:prettier/recommended 的配置需要注意的是,一定要放在最后。因为extends中后引入的规则会覆盖前面的规则。也就是说你可以在.prettierrc.js 中定义自己的风格代码。到时候,本地的prettier插件会根据这个文件来格式化,项目安装的prettier也会根据该文件来格式化。且eslint的风格与prettier风格冲突的地方会以prettier为主

const {
    
     defineConfig } = require('eslint-define-config')

module.exports = defineConfig({
    
    
  /// ...
  extends: [
   'plugin:vue/essential',
    '@vue/standard'
    'plugin:prettier/recommended'
  ],
  // ...
})

项目地址:https://github.com/rui-rui-an/how_to_caeate_vue2

猜你喜欢

转载自blog.csdn.net/weixin_43239880/article/details/130333559
今日推荐