新文章 网摘 文章 随笔 日记

梳理Vue使用实践

一、安装Visual Studio Code
下载地址: https://code.visualstudio.com/

二、安装NodeJS
下载地址:http://nodejs.cn/download/

三、安装webpack,打包工具
npm install webpack -g

四、安装vue-cli,vue脚手架项目初始化工具
npm install vue-cli -g

五、使用淘宝镜像
npm install -g cnpm --registry=https://registry.npm.taobao.org

六、安装Yarn
npm i yarn -g --verbose

七、创建项目
vue init webpack 项目名称
进入项目要目录执行: yarn install (也可以用 npm install,或淘宝 cnpm install,我们这里用 yarn 会快一点) 安装依赖包

八、启动项目:npm run dev 或 yarn start

九、导入项目:打开 Visual Studio Code,File --> add Folder to Workspace,导入我们的项目

十、安装 Element
npm i element-ui -S
或yarn add element-ui

在main.js中:

import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'

Vue.us(ElementUI)

十一、在views目录中放置页面,在components中放置全局性的组件,在router/index.js中放置路由。

十二、安装CSS
1.安装依赖
因为后续会用到 SCSS 编写页面样式,所以先安装好 SCSS。

yarn add sass-loader node-sass --dev
2.添加配置
在build文件夹下的webpack.base.conf.js的 rules 标签下添加配置。

{
  test: /\.scss$/,
  loaders: ['style', 'css', 'sass']
}
3.如何使用
在页面代码 style 标签中把 lang 设置成 scss 即可。

<style lang="scss">

</style>

十三、安装 axios
yarn add axios

使用:
import axios from 'axios'

adios.get('url')
.then(function(res){
alert(res.data);
})catch(function(res){
alert(res);
});


十四、安装Mock.js
yarn add mockjs --dev

使用:
import Mock from 'mockjs';

Mock.mock('http://localhost:8080/user',{
'name':'@name',
'email':@email,
'age!1-10':5
});

调用:
import mock from '@/mock/mock.js'

axios.get('http://localhost:8080/uer')
.then(function(res){
alert(JSON.stringify(res.data));
}.catch(function(res){
alert(res);
});


十五:封装 axios 模块
1、全局配置:
/utils/global.js:

/**
* 全局常量、方法封装模块
* 通过原型挂载到Vue属性
* 通过 this.Global 调用
*/

// 后台管理系统服务器地址
export const baseUrl = 'http://localhost:8001'
// 系统数据备份还原服务器地址
export const backupBaseUrl = 'http://localhost:8002'

export default {
baseUrl,
backupBaseUrl
}

2、/http/config.js:


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

export default {
method: 'get',
// 基础url前缀
baseUrl: baseUrl,
// 请求头信息
headers: {
'Content-Type': 'application/json;charset=UTF-8'
},
// 参数
data: {},
// 设置超时时间
timeout: 10000,
// 携带凭证
withCredentials: true,
// 返回数据类型
responseType: 'json'
}

3、/http/axios.js:


import axios from 'axios';
import config from './config';
import Cookies from "js-cookie";
import router from '@/router'

// 使用vuex做全局loading时使用
// import store from '@/store'

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')
// 1. 请求开始的时候可以结合 vuex 开启全屏 loading 动画
// console.log(store.state.loading)
// console.log('准备发送请求...')

// 2. 带上token
if (token) {
config.headers.token = token
} else {
// 重定向到登录页面
router.push('/login')
}

// 3. 根据请求方法,序列化传来的参数,根据后端需求是否序列化
if (config.method === 'post') {
// if (config.data.__proto__ === FormData.prototype
// || config.url.endsWith('path')
// || config.url.endsWith('mark')
// || config.url.endsWith('patchs')
// ) {

// } else {
// config.data = qs.stringify(config.data)
// }
}

return config
},

error => {
// 请求错误时
console.log('request:', error)

// 1. 判断请求超时
if (error.code === 'ECONNABORTED' && error.message.indexOf('timeout') !== -1) {
console.log('timeout请求超时')
// return service.request(originalRequest);// 再重复请求一次
}

// 2. 需要重定向到错误页面
const errorInfo = error.response
console.log(errorInfo)
if (errorInfo) {
error = errorInfo.data // 页面那边catch的时候就能拿到详细的错误信息,看最下边的Promise.reject
const errorStatus = errorInfo.status; // 404 403 500 ...
router.push({
path: `/error/${errorStatus}`
})
}
return Promise.reject(error) // 在调用的那边可以拿到(catch)你想返回的错误信息
}
)

// response 收到响应后拦截器作统一预处理
instance.interceptors.response.use(
response => {
let data;
// IE9时response.data是undefined,因此需要使用response.request.responseText(Stringify后的字符串)
if (response.data == undefined) {
data = JSON.parse(response.request.responseText)
} else {
data = response.data
}

// 根据返回的code值来做不同的处理
switch (data.rc) {
case 1:
console.log(data.desc)
break;
case 0:
store.commit('changeState')
// console.log('登录成功')
default:
}
// 若不是正确的返回code,且已经登录,就抛出错误
// const err = new Error(data.desc)
// err.data = data
// err.response = response
// throw err

return 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)
})
})
}


4、集成api入口/http/api.js:

/*
* 接口统一集成模块
*/
import * as login from './moudules/login'
import * as user from './moudules/user'
import * as dept from './moudules/dept'
import * as role from './moudules/role'
import * as menu from './moudules/menu'
import * as dict from './moudules/dict'
import * as log from './moudules/log'


// 默认全部导出
export default {
login,
user,
dept,
role,
menu,
dict,
log
}

5、挂载api到Vue原型方法上/http/index.js:

// 导入所有接口
import api from './api'

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

install.installed = true;

Object.defineProperties(Vue.prototype, {
// 注意,此处挂载在 Vue 原型的 $api 对象上
$api: {
get() {
return api
}
}
})
}

export default install

6、具体某个api,例如/http/modules/user.js:

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
})
}
// 查找用户的菜单权限标识集合
export const findPermissions = (params) => {
return axios({
url: '/user/findPermissions',
method: 'get',
params
})
}


7、调用:
在入口/main.js中:

import Vue from 'vue'
import api from './http'


Vue.use(api)


具体使用的时候:

this.$api.user.findPage(this.pageRequest).then((res) => {
this.pageResult = res.data
this.findUserRoles()
}).then(data!=null?data.callback:'')

 

十六:安装js-cookie

yarn add js-cookie

十七、封装Mock:

1、/http/index.js:

import Mock from 'mockjs'
import { baseUrl } from '@/utils/global'
import * as login from './modules/login'
import * as user from './modules/user'
import * as role from './modules/role'
import * as dept from './modules/dept'
import * as menu from './modules/menu'
import * as dict from './modules/dict'
import * as log from './modules/log'

// 1. 开启/关闭[所有模块]拦截, 通过调[openMock参数]设置.
// 2. 开启/关闭[业务模块]拦截, 通过调用fnCreate方法[isOpen参数]设置.
// 3. 开启/关闭[业务模块中某个请求]拦截, 通过函数返回对象中的[isOpen属性]设置.
// let openMock = true
let openMock = true
fnCreate(login, openMock)
fnCreate(user, openMock)
fnCreate(role, openMock)
fnCreate(dept, openMock)
fnCreate(menu, openMock)
fnCreate(dict, openMock)
fnCreate(log, openMock)

/**
* 创建mock模拟数据
* @param {*} mod 模块
* @param {*} isOpen 是否开启?
*/
function fnCreate (mod, isOpen = true) {

if (isOpen) {
for (var key in mod) {
((res) => {
if (res.isOpen !== false) {
let url = baseUrl
if(!url.endsWith("/")) {
url = url + "/"
}
url = url + res.url
Mock.mock(new RegExp(url), res.type, (opts) => {
opts['data'] = opts.body ? JSON.parse(opts.body) : null
delete opts.body
console.log('\n')
console.log('%cmock拦截, 请求: ', 'color:blue', opts)
console.log('%cmock拦截, 响应: ', 'color:blue', res.data)
return res.data
})
}
})(mod[key]() || {})
}
}
}

2、具体某个mock,例如/mock/modules/user.js:

/*
* 用户管理模块
*/

// 保存
export function save() {
return {
url: 'user/save',
type: 'post',
data: {
"code": 200,
"msg": null,
"data": 1
}
}
}
// 批量删除
export function batchDelete() {
return {
url: 'user/delete',
type: 'post',
data: {
"code": 200,
"msg": null,
"data": 1
}
}
}
// 分页查询
export function findPage(params) {
let findPageData = {
"code": 200,
"msg": null,
"data": {}
}
let pageNum = 1
let pageSize = 8
if(params !== null) {
// pageNum = params.pageNum
}
if(params !== null) {
// pageSize = params.pageSize
}
let content = this.getContent(pageNum, pageSize)
findPageData.data.pageNum = pageNum
findPageData.data.pageSize = pageSize
findPageData.data.totalSize = 50
findPageData.data.content = content
return {
url: 'user/findPage',
type: 'post',
data: findPageData
}
}
export function getContent(pageNum, pageSize) {
let content = []
for(let i=0; i<pageSize; i++) {
let obj = {}
let index = ((pageNum - 1) * pageSize) + i + 1
obj.id = index
obj.name = 'kitty' + index
obj.password = '9ec9750e709431dad22365cabc5c625482e574c74adaebba7dd02f1129e4ce1d'
obj.salt = 'YzcmCZNvbXocrsz9dm8e'
obj.email = 'kitty' + index +'@qq.com'
obj.mobile = '18688982323'
obj.status = 1
obj.deptId = 12
obj.deptName = '技术部'
obj.status = 1
if(i % 2 === 0) {
obj.deptId = 13
obj.deptName = '市场部'
}
obj.createBy= 'admin'
obj.createTime= '2018-08-14 11:11:11'
obj.createBy= 'admin'
obj.createTime= '2018-09-14 12:12:12'
content.push(obj)
}
return content
}
// 查找用户的菜单权限标识集合
export function findPermissions() {
let permsData = {
"code": 200,
"msg": null,
"data": [
null,
"sys:user:view",
"sys:menu:delete",
"sys:dept:edit",
"sys:dict:edit",
"sys:dict:delete",
"sys:menu:add",
"sys:user:add",
"sys:log:view",
"sys:dept:delete",
"sys:role:edit",
"sys:role:view",
"sys:dict:view",
"sys:user:edit",
"sys:user:delete",
"sys:dept:view",
"sys:dept:add",
"sys:role:delete",
"sys:menu:view",
"sys:menu:edit",
"sys:dict:add",
"sys:role:add"
]
}
return {
url: 'user/findPermissions',
type: 'get',
data: permsData
}
}

3、在使用mock的地方(调用api的地方),例如Login.vue:

import mock from '@/mock/index.js';

就可以了,然后正常的api请求就会被拦截并返回mock数据


十八、添加导航守卫(检查是否登录)

在@/router/index.js中router定义之后添加:

router.beforeEach((to, from, next) => {
// 登录界面登录成功之后,会把用户信息保存在会话
// 存在时间为会话生命周期,页面关闭即失效。
let token = Cookies.get('token')
let userName = sessionStorage.getItem('user')
if (to.path === '/login') {
// 如果是访问登录界面,如果用户会话信息存在,代表已登录过,跳转到主页
if(token) {
next({ path: '/' })
} else {
next()
}
} else {
if (!token) {
// 如果访问非登录界面,且户会话信息不存在,代表未登录,则跳转到登录界面
next({ path: '/login' })
} else {
// 加载动态菜单和路由
addDynamicMenuAndRoutes(userName, to, from)
next()
}
}
})

十九、路由嵌套:

在@/router/index.js中类似如下(children部分):

const router = new Router({
routes: [
{
path: '/',
name: '首页',
component: Home,
children: [
{
path: '',
name: '系统介绍',
component: Intro,
meta: {
icon: 'fa fa-home fa-lg',
index: 0
}
}
]
},
{
path: '/login',
name: '登录',
component: Login
},
{
path: '/404',
name: 'notFound',
component: NotFound
}
]
})

使用:
<el-menu-item index="1-2" @click="$router.push('menu')>菜单管理</el-menu-item>


二十、国际化支持

1、安装依赖
yarn add vue-i18n

2、在 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

3、在 assets 目录下面创建多语言文件。例如src\assets\languages\zh_cn.json:

{
"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": "重置"

},
"sys": {
"sysMng": "系统管理",
"userMng": "用户管理",
"deptMng": "机构管理",
"roleMng": "角色管理",
"menuMng": "菜单管理",
"logMng": "日志管理",
"sysMonitor": "系统监控"
}
}

4、在 main.js 中引入 i18n 并注入到 vue 对象中。

import i18n from './i18n'

new Vue({
el: '#app',
i18n,
router,
store,
render: h => h(App)
});


5、字符引用:

{{$t("common.blog")}}


6、语言切换:

<el-menu-item index="2" v-popover:popover-lang>
<!-- 语言切换 -->
<li style="color:#fff;" class="fa fa-language fa-lg"></li>
<el-popover ref="popover-lang" placement="bottom-start" trigger="click" v-model="langVisible">
<div class="lang-item" @click="changeLanguage('zh_cn')">简体中文</div>
<div class="lang-item" @click="changeLanguage('en_us')">English</div>
</el-popover>
</el-menu-item>


// 语言切换
changeLanguage(lang) {
lang === '' ? 'zh_cn' : lang
this.$i18n.locale = lang
this.langVisible = false
}

二十一、更换皮肤主题

1.安装主题工具

yarn add element-theme -g --dev

2.安装chalk主题

yarn add element-theme-chalk -D

3.初始化变量文件
主题生成工具安装成功后,如果全局安装可以在命令行里通过 et 调用工具,如果安装在当前目录下,需要通过 node_modules/.bin/et 访问到命令。执行 -i 初始化变量文件。默认输出到 element-variables.scss,当然你可以传参数指定文件输出目录。

node_modules/.bin/et -i

4、修改主题色
在 element-variables.scss 文件里修改 $–color-primary:#4b5f6e,即你想要的主题颜色


5、编译主题
执行主题编译命令生成主题,根目录会生成一个theme的文件夹 。

node_modules/.bin/et


6、引入自定义主题
把生成的主题按颜色改名放置 src/theme 目录下。

在 main.js 中 import ‘所在路径/index.css’。

7、动态换肤

添加@/components/ThemePicker/index.vue

<template>
<el-color-picker
class="theme-picker"
popper-class="theme-picker-dropdown"
v-model="theme"
:size="size">
</el-color-picker>
</template>

<script>

const version = require('element-ui/package.json').version // element-ui version from node_modules
const ORIGINAL_THEME = '#409EFF' // default color

export default {
name: 'ThemePicker',
props: {
default: { // 初始化主题,可由外部传入
type: String,
default: null
},
size: { // 初始化主题,可由外部传入
type: String,
default: 'small'
}
},
data() {
return {
chalk: '', // content of theme-chalk css
theme: ORIGINAL_THEME,
showSuccess: true // 是否弹出换肤成功消息
}
},
mounted() {
if(this.default != null) {
this.theme = this.default
this.$emit('onThemeChange', this.theme)
this.showSuccess = false
}
},
watch: {
theme(val, oldVal) {
if (typeof val !== 'string') return
const themeCluster = this.getThemeCluster(val.replace('#', ''))
const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))
console.log(themeCluster, originalCluster)
const getHandler = (variable, id) => {
return () => {
const originalCluster = this.getThemeCluster(ORIGINAL_THEME.replace('#', ''))
const newStyle = this.updateStyle(this[variable], originalCluster, themeCluster)

let styleTag = document.getElementById(id)
if (!styleTag) {
styleTag = document.createElement('style')
styleTag.setAttribute('id', id)
document.head.appendChild(styleTag)
}
styleTag.innerText = newStyle
}
}

const chalkHandler = getHandler('chalk', 'chalk-style')

if (!this.chalk) {
const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`
this.getCSSString(url, chalkHandler, 'chalk')
} else {
chalkHandler()
}

const styles = [].slice.call(document.querySelectorAll('style'))
.filter(style => {
const text = style.innerText
return new RegExp(oldVal, 'i').test(text) && !/Chalk Variables/.test(text)
})
styles.forEach(style => {
const { innerText } = style
if (typeof innerText !== 'string') return
style.innerText = this.updateStyle(innerText, originalCluster, themeCluster)
})

// 响应外部操作
this.$emit('onThemeChange', val)
if(this.showSuccess) {
this.$message({
message: '换肤成功',
type: 'success'
})
} else {
this.showSuccess = true
}
}
},
methods: {
updateStyle(style, oldCluster, newCluster) {
let newStyle = style
oldCluster.forEach((color, index) => {
newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])
})
return newStyle
},

getCSSString(url, callback, variable) {
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')
callback()
}
}
xhr.open('GET', url)
xhr.send()
},

getThemeCluster(theme) {
const tintColor = (color, tint) => {
let red = parseInt(color.slice(0, 2), 16)
let green = parseInt(color.slice(2, 4), 16)
let blue = parseInt(color.slice(4, 6), 16)

if (tint === 0) { // when primary color is in its rgb space
return [red, green, blue].join(',')
} else {
red += Math.round(tint * (255 - red))
green += Math.round(tint * (255 - green))
blue += Math.round(tint * (255 - blue))

red = red.toString(16)
green = green.toString(16)
blue = blue.toString(16)

return `#${red}${green}${blue}`
}
}

const shadeColor = (color, shade) => {
let red = parseInt(color.slice(0, 2), 16)
let green = parseInt(color.slice(2, 4), 16)
let blue = parseInt(color.slice(4, 6), 16)

red = Math.round((1 - shade) * red)
green = Math.round((1 - shade) * green)
blue = Math.round((1 - shade) * blue)

red = red.toString(16)
green = green.toString(16)
blue = blue.toString(16)

return `#${red}${green}${blue}`
}

const clusters = [theme]
for (let i = 0; i <= 9; i++) {
clusters.push(tintColor(theme, Number((i / 10).toFixed(2))))
}
clusters.push(shadeColor(theme, 0.1))
return clusters
}
}
}
</script>

<style>
.theme-picker .el-color-picker__trigger {
vertical-align: middle;
}

.theme-picker-dropdown .el-color-dropdown__link-btn {
display: none;
}
</style>


引入:
import ThemePicker from "@/components/ThemePicker"

export default {
components:{
ThemePicker
}}


使用:
<!-- 主题切换 -->
<theme-picker class="theme-picker" :default="themeColor" @onThemeChange="onThemeChange"></theme-picker>

 

posted @ 2020-12-22 14:17  岭南春  阅读(235)  评论(0)    收藏  举报