第十一章 前端开发环境搭建

一、前端开发环境
  1. Visual Studio Code
  2. Node JS
  3. Webpack:npm install webpack -g
  4. vue-cli:npm install vue-cli -g
  5. 淘宝镜像:npm install -g cnpm --registry=https://registry.npm.taobao.org
  6. Yarn:npm i yarn -g -verbose
二、创建项目
vue init webpack icupo-web
npm install
三、安装Element UI
npm install element-ui

按照官网的引入方式在main.js中引入:

import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
...
Vue.use(ElementUI)
四、安装scss
# 注意安装的版本
npm install node-sass@4.14.1
npm install sass-loader@7.3.1

配置build/webpack.base.conf.js,注意这个不要添加

      {
        test: /\.scss$/,
        loaders: ['style', 'css', 'sass']
      }
五、安装axios与js-cookie

对axios的封装有几个好处:

  • 统一Url配置
  • 统一Api请求
  • request拦截器,加入请求头
  • response拦截器,统一错误处理,页面重定向
  • 结合vuex做全局的loading动画,或错误处理
  • 将axios封装成vue插件

5.1 安装axios和js-cookie

npm install axios
npm install js-cookie

5.2 定义全局常量文件src/utils/global.js,并挂载到Vue,通过this.global调用常量的值。

// 后台管理系统服务器地址
export const baseUrl = 'http://localhost:8001'

// 系统数据备份还原服务器地址
export const backupBaseUrl = 'http://localhost:8002'

export default {
  baseUrl,
  backupBaseUrl
}

在main.js中挂载

import global from '@/utils/global'
...
Vue.prototype.global = global

5.3 配置axios,src/http/config.js,一些默认的配置项

import { baseUrl } from '@/utils/global'

export default {
    method: 'get',
    baseUrl: baseUrl,
    Headers: {
        'Content-Type': 'application/json;charset=UTF-8'
    },
    data: {},
    timeout: 300000,
    withCredentials: true,
    responseType: 'json'
}

5.4 axios的请求与响应的处理,src/http/axios.js

  • 导入配置文件的信息到axios对象
  • 发送请求的时候携带token,如果token不存在,则重定向到登录页面
  • 统一处理响应
import axios from 'axios'
import config from './config'
import Cookies from 'js-cookie'
import router from '../router'

export default function $axios(options) {
    return new Promise((resolve, reject) => {
        const instance = axios.create({
            baseURL: config.baseUrl,
            headers: config.headers,
            timeout: config.timeout,
            withCredentials: config.withCredentials
        })
        // request 请求拦截器
        instance.interceptors.request.use(
            config => {
                let token = Cookies.get('token')
                if (token) {
                    config.headers.Authorization = 'Bearer ' + token
                } else {
                    router.push('/login')
                }
                return config
            },
            error => {
                return Promise.reject(error)
            }
        )
        // response 响应拦截器
        instance.interceptors.response.use(
            response => {
                return response.data
            },
            err => {
                if (err && err.response) {
                    switch (err.response.status) {
                        case 400:
                            err.message = '请求错误'
                            break
                        case 401:
                            err.message = '未授权,请登录'
                            break
                        case 403:
                            err.message = '拒绝访问'
                            break
                        case 404:
                            err.message = `请求地址出错: ${err.response.config.url}`
                            break
                        case 408:
                            err.message = '请求超时'
                            break
                        case 500:
                            err.message = '服务器内部错误'
                            break
                        case 501:
                            err.message = '服务未实现'
                            break
                        case 502:
                            err.message = '网关错误'
                            break
                        case 503:
                            err.message = '服务不可用'
                            break
                        case 504:
                            err.message = '网关超时'
                            break
                        case 505:
                            err.message = 'HTTP版本不受支持'
                            break
                        default:
                    }
                }
                console.error(err)
                return Promise.reject(err)
            }
        )
        // 请求处理
        instance(options).then(res => {
            resolve(res)
            return false
        }).catch(error => {
            reject(error)
        })
    })
}

5.5 挂载api,可以通过 "this.$api.模块.方法" 的方式调用API。

  • src/http/index.js
// 导入所有接口
import api from './api'

const install = Vue => {
  if (install.installed) {
    return
  }

  install.installed = true

  Object.defineProperties(Vue.prototype, {
    $api: {
      get () {
        return api
      }
    }
  })
}

export default install

  • src/http/api.js
/* 
 * 接口统一集成模块
 */
import * as login from './modules/login'
import * as user from './modules/user'
import * as dept from './modules/dept'
import * as role from './modules/role'
import * as menu from './modules/menu'
import * as dict from './modules/dict'
import * as config from './modules/config'
import * as log from './modules/log'
import * as loginlog from './modules/loginlog'


// 默认全部导出
export default {
    login,
    user,
    dept,
    role,
    menu,
    dict,
    config,
    log,
    loginlog
}
  • main.js
import Vue from 'vue'
import App from './App'
import router from './router'
import global from './utils/global'
import api from './http'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'

Vue.config.productionTip = false

Vue.use(api)
Vue.use(ElementUI)

Vue.prototype.global = global

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})

5.6 一些api的案例

import axios from '../axios'

/* 
 * 系统配置模块
 */

// 保存
export const save = (data) => {
    return axios({
        url: '/config/save',
        method: 'post',
        data
    })
}
// 删除
export const batchDelete = (data) => {
    return axios({
        url: '/config/delete',
        method: 'post',
        data
    })
}
// 分页查询
export const findPage = (data) => {
    return axios({
        url: '/config/findPage',
        method: 'post',
        data
    })
}
import axios from '../axios'

/* 
 * 机构管理模块
 */

// 保存
export const save = (data) => {
    return axios({
        url: '/dept/save',
        method: 'post',
        data
    })
}
// 删除
export const batchDelete = (data) => {
    return axios({
        url: '/dept/delete',
        method: 'post',
        data
    })
}
// 查询机构树
export const findDeptTree = () => {
    return axios({
        url: '/dept/findTree',
        method: 'get'
    })
}
import axios from '../axios'

/* 
 * 字典管理模块
 */

// 保存
export const save = (data) => {
    return axios({
        url: '/dict/save',
        method: 'post',
        data
    })
}
// 删除
export const batchDelete = (data) => {
    return axios({
        url: '/dict/delete',
        method: 'post',
        data
    })
}
// 分页查询
export const findPage = (data) => {
    return axios({
        url: '/dict/findPage',
        method: 'post',
        data
    })
}
import axios from '../axios'

/* 
 * 操作日志模块
 */

// 删除
export const batchDelete = (data) => {
    return axios({
        url: '/log/delete',
        method: 'post',
        data
    })
}
// 分页查询
export const findPage = (data) => {
    return axios({
        url: '/log/findPage',
        method: 'post',
        data
    })
}
import axios from '../axios'

/* 
 * 系统登录模块
 */

// 登录
export const login = data => {
    return axios({
        url: 'login',
        method: 'post',
        data
    })
}

// 登出
export const logout = () => {
    return axios({
        url: 'logout',
        method: 'get'
    })
}
import axios from '../axios'

/* 
 * 操作日志模块
 */

// 删除
export const batchDelete = (data) => {
    return axios({
        url: '/loginlog/delete',
        method: 'post',
        data
    })
}
// 分页查询
export const findPage = (data) => {
    return axios({
        url: '/loginlog/findPage',
        method: 'post',
        data
    })
}
import axios from '../axios'

/* 
 * 菜单管理模块
 */

 // 保存
export const save = (data) => {
    return axios({
        url: '/menu/save',
        method: 'post',
        data
    })
}
// 删除
export const batchDelete = (data) => {
    return axios({
        url: '/menu/delete',
        method: 'post',
        data
    })
}
// 查找导航菜单树
export const findNavTree = (params) => {
    return axios({
        url: '/menu/findNavTree',
        method: 'get',
        params
    })
}
// 查找导航菜单树
export const findMenuTree = () => {
    return axios({
        url: '/menu/findMenuTree',
        method: 'get'
    })
}
import axios from '../axios'

/* 
 * 角色管理模块
 */

// 保存
export const save = (data) => {
    return axios({
        url: '/role/save',
        method: 'post',
        data
    })
}
// 删除
export const batchDelete = (data) => {
    return axios({
        url: '/role/delete',
        method: 'post',
        data
    })
}
// 分页查询
export const findPage = (data) => {
    return axios({
        url: '/role/findPage',
        method: 'post',
        data
    })
}
// 查询全部
export const findAll = () => {
    return axios({
        url: '/role/findAll',
        method: 'get'
    })
}
// 查询角色菜单集合
export const findRoleMenus = (params) => {
    return axios({
        url: '/role/findRoleMenus',
        method: 'get',
        params
    })
}
// 保存角色菜单集合
export const saveRoleMenus = (data) => {
    return axios({
        url: '/role/saveRoleMenus',
        method: 'post',
        data
    })
}
import axios from '../axios'

/* 
 * 用户管理模块
 */

// 保存
export const save = (data) => {
    return axios({
        url: '/user/save',
        method: 'post',
        data
    })
}
// 删除
export const batchDelete = (data) => {
    return axios({
        url: '/user/delete',
        method: 'post',
        data
    })
}
// 分页查询
export const findPage = (data) => {
    return axios({
        url: '/user/findPage',
        method: 'post',
        data
    })
}
// 导出Excel用户信息
export const exportUserExcelFile = (data) => {
    return axios({
        url: '/user/exportUserExcelFile',
        method: 'post',
        data
    })
}
// 查找用户的菜单权限标识集合
export const findPermissions = (params) => {
    return axios({
        url: '/user/findPermissions',
        method: 'get',
        params
    })
}
// 根据用户名查找
export const findByName = (params) => {
    return axios({
        url: '/user/findByName',
        method: 'get',
        params
    })
}
// 更新用户密码
export const updatePassword = (params) => {
    return axios({
        url: '/user/updatePassword',
        method: 'get',
        params
    })
}

3.4 登录逻辑

login() {
  this.$api.login.login().then(function(res) {
    Cookies.set('token', res.token)
    router.push('/')
  }).catch(function(res) {
    // 其它处理
  })
}
四、国际化
  1. 安装依赖
npm install vue-i18n@8.26.5
  1. 配置src/i18n/index.js
import Vue from 'vue'
import VueI18n from 'vue-i18n'

Vue.use(VueI18n)

// 注册i18n实例并引入语言文件,文件格式等下解析
const i18n = new VueI18n({
  locale: 'zh_cn',
  messages: {
    'zh_cn': require('@/assets/languages/zh_cn.json'),
    'en_us': require('@/assets/languages/en_us.json')
  }
})

export default i18n

{
    "common": {
        "home": "首页",
        "login": "登录",
        "logout": "退出登录",
        "doc": "文档",
        "blog": "博客",
        "projectRepo": "项目",
        "myMsg": "我的消息",
        "config": "系统配置",           
        "backup": "备份",
        "restore": "还原",
        "backupRestore": "备份还原",
        "versionName": "版本名称",             
        "exit": "退出"
    },
    "action": {
        "operation": "操作",
        "add": "新增",
        "edit": "编辑",
        "delete": "删除",
        "batchDelete": "批量删除",
        "search": "查询",
        "loading": "拼命加载中",
        "submit": "提交",
        "comfirm": "确定",
        "cancel": "取消",
        "reset": "重置"
        
    }
}
{
    "common": {
        "home": "Home",
        "login": "Login",
        "logout": "Logout",
        "doc": "Document",
        "blog": "Blog",
        "projectRepo": "Project",
        "myMsg": "My Message",
        "config": "Config",
        "backup": "Backup",  
        "restore": "Restore",  
        "backupRestore": "Backup Restore",  
        "versionName": "Version",  
        "exit": "Exit"
    },
    "action": {
        "operation": "Operation",
        "add": "Add",
        "edit": "Edit",
        "delete": "Delete",
        "batchDelete": "Batch Delete",
        "search": "Search",
        "loading": "loading",
        "submit": "Submit",
        "comfirm": "Comfirm",
        "cancel": "Cancel",
        "reset": "Reset"
    }
}
import Vue from 'vue'
import App from './App'
import router from './router'
import i18n from './i18n'
import global from './utils/global'
import api from './http'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'

Vue.config.productionTip = false

Vue.use(api)
Vue.use(ElementUI)

Vue.prototype.global = global

/* eslint-disable no-new */
new Vue({
  el: '#app',
  i18n,
  router,
  components: { App },
  template: '<App/>'
})

  1. 切换语言函数:
changeLanguage(lang) {
  lang === '' ? 'zh_cn' : lang
  this.$i18n.locale = lang
}
  1. 使用方法:
# html中使用
{{$t('common.doc')}}
# js中使用
i18n.t('message.timeout')
五、全局状态
  1. 安装
npm install vuex@3.6.2
  1. 编写配置文件,src/store/index.js
import Vue from 'vue'
import vuex from 'vuex'

Vue.use(vuex);

// 引入子模块
import app from './modules/app'
import tab from './modules/tab'
import user from './modules/user'
import menu from './modules/menu'

const store = new vuex.Store({
    modules: {
        app: app,
        tab: tab,
        user: user,
        menu: menu
    }
})

export default store
  1. app.js。是属于应用内的全局性的配置,比如主题色、导航栏收缩状态等,详见注释。
export default {
  state: {
    test: false, // 测试
    menuRouteLoaded: false // 菜单和路由是否已经加载
  },
  getters: {
    test (state) {
      return state.test
    }
  },
  mutations: {
    setTest (state, payload) {
      state.test = payload.test
    },
    menuRouteLoaded (state, menuRouteLoaded) { // 改变菜单和路由的加载状态
      state.menuRouteLoaded = menuRouteLoaded
    }
  },
  actions: {
  }
}

import Vue from 'vue'
import App from './App'
import router from './router'
import i18n from './i18n'
import store from './store'
import global from './utils/global'
import api from './http'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'

Vue.config.productionTip = false

Vue.use(api)
Vue.use(ElementUI)

Vue.prototype.global = global

/* eslint-disable no-new */
new Vue({
  el: '#app',
  i18n,
  router,
  store,
  components: { App },
  template: '<App/>'
})

  1. 通过computed计算属性引入store属性,
computed: {
  ...mapState({
    ***: state => state.app.***
  })
}
  1. 通过语句this.$store.commit('mothodName', {})来修改值
六、全站配置
  1. 样式文件src/assets/css/site.css
# 内容
* {
    margin: 0;
    padding: 0;
    user-select: none;
}

# main.js中引入
import './assets/css/site.css'
  1. 全局图片文件src/assets/img/site。
  2. App.vue
<template>
  <div id="app">
    <router-view/>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

<style>
</style>
  1. vscode调整tab为2个空格
# .eslintrc.js -> rules中添加
"indent": ["error", "tab"]
七、自定义图标功能

打开阿里icon,注册 >登录>图标管理>我的项目。项目名称:el-icon-third。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容