node

node

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。
javascript/jQuery

javascript/jQuery

一种直译式脚本语言,是一种动态类型、弱类型、基于原型的语言,内置支持类型。
MongoDB

MongoDB

MongoDB 是一个基于分布式文件存储的数据库
openstack

openstack

OpenStack是一个由NASA(美国国家航空航天局)和Rackspace合作研发并发起的,以Apache许可证授权的自由软件和开放源代码项目。
VUE

VUE

一套构建用户界面的渐进式框架。与其他重量级框架不同的是,Vue 采用自底向上增量开发的设计。
bootstrap

bootstrap

Bootstrap is the most popular HTML, CSS, and JS framework for developing responsive, mobile first projects on the web.
HTML

HTML

超文本标记语言,标准通用标记语言下的一个应用。
CSS/SASS/SCSS/Less

CSS/SASS/SCSS/Less

层叠样式表(英文全称:Cascading Style Sheets)是一种用来表现HTML(标准通用标记语言的一个应用)或XML(标准通用标记语言的一个子集)等文件样式的计算机语言。
PHP

PHP

PHP(外文名:PHP: Hypertext Preprocessor,中文名:“超文本预处理器”)是一种通用开源脚本语言。语法吸收了C语言、Java和Perl的特点,利于学习,使用广泛,主要适用于Web开发领域。PHP 独特的语法混合了C、Java、Perl以及PHP自创的语法。它可以比CGI或者Perl更快速地执行动态网页。用PHP做出的动态页面与其他的编程语言相比,PHP是将程序嵌入到HTML(标准通用标记语言下的一个应用)文档中去执行,执行效率比完全生成HTML标记的CGI要高许多;PHP还可以执
每天进步一点点

每天进步一点点

乌法把门的各累笑寂静
求职招聘

求职招聘

猎头招聘专用栏目
Python

Python

一种解释型、面向对象、动态数据类型的高级程序设计语言。

怎么渲染axios获取的数据呢?谢谢大佬

lopo1983 回复了问题 • 2 人关注 • 1 个回复 • 2094 次浏览 • 2018-12-17 13:11 • 来自相关话题

bootstrap4.x+vue2.x 文件上传

lopo1983 发表了文章 • 0 个评论 • 2166 次浏览 • 2018-03-16 22:43 • 来自相关话题

template<template>
<div class="upload">
<label class="mb-0" :class="hasSlot ? '' : 'custom-file'" @drop.prevent="onDrop">
<input type="file" :class="hasSlot ? 'd-none' : 'custom-file-input'" @change="onPicker" :multiple="multiple" :accept="accept" v-if="refresh">
<span v-if="hasSlot"><slot></slot></span>
<span v-else class="custom-file-control">选择文件</span>
</label>
<span class="help text-secondary" :class="!!helpblock?`d-block mt-2`:'ml-3'" v-if="help">{{help}}</span>
<cardlayer :class="{'mt-3':files.length}">
<card class="upload-item" v-for="(item,idx) in files" :key="idx" :style="`flex: 0 0 ${1/col*100}%`">
<cardbody>
<div class="upload-cancel">
<div class="icon-mix">
<icon icon="shanchu" @click.native="onCancel(idx)" title="删除"></icon>
<icon icon="reupload" @click.native="upload" v-if="item.stype=='danger'" title="重传"></icon>
</div>
</div>
<div class="upload-preview" :style="{backgroundImage:`url(${item._base64 || item.base64})`}"></div>
<img class="card-img-top" :src="square">
</cardbody>
<cardfooter :class="item.stype!=''&&`bg-${item.stype}`">
<progressbar class="upload-progress" v-bind="item" size="xs"></progressbar>
<div class="d-flex upload-info ">
<div class="text-truncate mb-0 float-left">{{item.name}}</div>
</div>
</cardfooter>
</card>
</cardlayer>
<!--<div class="file-holder">
<div v-for="(item,idx) in files" :key="idx">
<a href="javascript:;" @click="onCancel(idx)">取消</a>
<div class="holder" :style="{backgroundImage:`url(${item.base64})`}">{{ item.name }}</div>
<progressbar v-bind="item" size="xs"></progressbar>
</div>
</div>-->
<button type="button" class="btn btn-ces mt-3" @click="upload" v-if="!autoUpload&&files.length">上传</button>
</div>
</template>javascript<script>
/**
* bootstrap 4.x --> components --> uploader
*
* @param {Boolean} multiple 多选
* @param {Number} max 多选最大值
* @param {Array} maxSize 最大尺寸[width,height] ,不限制不传或者传 0
* @param {Array} allowMime 允许上传的 MIME 类型,默认不限制
* @param {String} url 上传URL
* @param {Boolean} autoUpload 是否选择文件后自动上传
* @param {Function} uploadMethod 自定义上传方法,必须返回Promise,成功resolve
* @param {Function|Object} extraParams
* @param {String} help 上传帮助文字
*
* @event remove 删除
* @event error 错误
* @event success 单个上传成功
* @event completed 全部上传成功
*
* @date 2017-10-24
*
* @requires 依赖 axios icon cards
*
* @version v1.0.0 beta
*
* @author www.bsfans.com 戏子 lopo
*
**/
import icon from './icon'
import progressbar from './progress'
import square from '@/assets/square.png'
import {
cardlayer,
card,
cardheader,
cardbody,
cardfooter
} from '@/components/comp/cards'
import axios from 'axios'

const IMAGE_MIME = ['image/png', 'image/jpg', 'image/gif', 'image/jpeg']

const fnTypeBase64 = text => {
const canvas = document.createElement('canvas')
canvas.width = 200
canvas.height = 200
const ctx = canvas.getContext('2d')
ctx.fillStyle = 'rgba(255,255,255,0.8)'
ctx.fillRect(0, 0, 200, 200)
ctx.stroke()
ctx.font = '40px monaco'
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText(text, 100, 100)
return canvas.toDataURL('image/jpeg')
}

export default {
name: 'uploader',
props: {
multiple: Boolean,
max: Number,
maxSize: {
type: Array,
default: () =>
},
allowMime: {
type: Array,
default: () =>
},
col: {
type: Number,
default: 6
},
help: String,
helpblock:{
type:Boolean,
default:false
},
url: String,
autoUpload: {
type: Boolean,
default: false
},
uploadMethod: Function,
extraParams: [Function, Object]
},
data() {
return {
files: ,
refresh: true,
square: square
}
},
components: {
progressbar,
icon,
cardlayer,
card,
cardheader,
cardbody,
cardfooter
},
methods: {
onPicked(files) {
if (this.max > 0 && this.files.length + files.length > this.max) {
this.$emit('error','图片太大')
return false
}
const isImage = type => IMAGE_MIME.findIndex(item => item == type) > -1
this.$emit('picked', files)
Array.from(files).forEach(file => {
const o = {
file,
name: file.name,
filesize: file.size,
type: file.name
.substring(file.name.lastIndexOf('.'))
.toLowerCase()
.replace('.', ''),
stype: '',
value: 0,
base64: '',
width: 0,
height: 0,
isimage: isImage(),
completed: false,
uploading: false
}

if (this.allowMime.length) {
if (this.allowMime.findIndex(item => item == file.type) === -1) {
return this.$emit('error')
}
}

const reader = new FileReader()
reader.onload = e => {
o.base64 = e.target.result
const idx = this.files.findIndex(item => {
return item.base64 == o.base64 && item.name == o.name
})
if (idx === -1) {
if (isImage(file.type)) {
const image = new Image()
image.onload = () => {
o.width = image.width
o.height = image.height
const [maxwidth, maxheight] = this.maxSize
if (maxwidth && maxwidth < o.width) {
this.$emit('error')
} else if (maxheight && maxheight < o.height) {
this.$emit('error')
} else {
this.files.push(o)
this.autoUpload && this.fnUpload(o)
}
}
image.src = o.base64
} else {
o._base64 = fnTypeBase64(o.type)
this.autoUpload && this.fnUpload(o)
this.files.push(o)
}
}
}
reader.onerror = () => {
this.$emit('error')
}
reader.readAsDataURL(file)
})
this.reset()
},
reset() {
this.refresh = false
this.$nextTick(() => {
this.refresh = true
})
},
onPicker(e) {
this.onPicked(e.target.files)
},
onDrop(e) {
this.onPicked(e.dataTransfer.files)
},
onCancel(idx) {
this.files.splice(idx, 1)
this.$emit('remove', idx)
},
upload() {
this.files.filter(item => !item.uploading).forEach(this.fnUpload)
},
clearFile() {
this.files =
},
fnUpload(o) {
o.uploading = true
if (this.uploadMethod) {
const p = this.uploadMethod(o)
if (p.then) {
p.then(res => {
o.stype = 'success'
o.value = 100
o.completed = true
this.$emit('success', res)
})
}
} else {
// console.log('no then')
const formData = new FormData()
formData.append('file', o.file)
const params =
typeof this.extraParams == 'function'
? this.extraParams(o)
: this.extraParams
Object.keys(params).forEach(key => formData.append(key, params[key]))
axios
.post(this.url, formData, {
onUploadProgress(evt) {
o.value = 100 * evt.loaded / evt.total
}
})
.then(
res => {
o.stype = 'success'
o.completed = true
this.$emit('success', res)
},
err => {
o.stype = 'danger'
this.$emit('error')
}
)
}
}
},
computed: {
accept() {
return this.allowMime.length ? this.allowMime.join(',') : ''
},
isCompleted() {
if (this.files.length) {
return this.files.filter(item => !item.completed).length == 0
} else {
return false
}
},
hasSlot() {
return !!this.$slots.default
}
},
watch: {
isCompleted(v) {
v && this.$emit('completed')
}
},
mounted() {
const DRAG_EVENTS = ['dragleave', 'drop', 'dragenter', 'dragover']
DRAG_EVENTS.forEach(evtName => {
document.addEventListener(evtName, e => e.preventDefault(), false)
})
}
}
</script>style(less)<style lang="less">
@import (reference) '../../assets/lib/css.less';
.upload {
input[type='file'] {
opacity: 0;
}
.custom-file {
.mgb(1rem);
}
&-progress {
.ps;
top: 0;
left: 0;
right: 0;
}
&-info {
.cp;
align-items: center;
}
/*&-progress {
.ppd;
width:0;
z-index: 2;
.bgc(@ces);
}*/
&-item {
.card-body {
overflow: hidden;
position: relative;
z-index: 1;
}
.card-footer {
.trs;
.pr;
.bgcw;
.pdy(0.3rem);
&[class*='bg'] {
.crw;
}
}
}
&-preview {
.ppd(1.25rem);
.bdr(@cr: rgba(0, 0, 0, 0.1));
background-repeat: no-repeat;
background-position: center center;
background-size: 100%;
}
&-cancel {
.mask;
.trs;
opacity: 0;
.bgc(rgba(0, 0, 0, 0.5));
z-index: 3;
.icon-mix {
.amid;
.iconfont + .iconfont {
.mgl(1.5rem);
}
}
.iconfont {
.fs(1.6rem);
.cp;
.trs;
.trf(scale(0));
.crw;
.trfo(50% 50%);
&:after {
content: '';
.bgc(rgba(0, 0, 0, 0.2));
width: 3rem;
height: 3rem;
.db;
.mask;
.trs;
z-index: -1;
left: -0.7rem;
top: -0.3rem;
.bdrrd(3rem);
}
&:hover:after {
.bgc(@ces);
}
}
.upload .card:hover & {
opacity: 1;
.iconfont {
.trf(scale(1));
}
}
}
}
</style>




? 查看全部

QQ图片20180316224810.png

template
<template>
<div class="upload">
<label class="mb-0" :class="hasSlot ? '' : 'custom-file'" @drop.prevent="onDrop">
<input type="file" :class="hasSlot ? 'd-none' : 'custom-file-input'" @change="onPicker" :multiple="multiple" :accept="accept" v-if="refresh">
<span v-if="hasSlot"><slot></slot></span>
<span v-else class="custom-file-control">选择文件</span>
</label>
<span class="help text-secondary" :class="!!helpblock?`d-block mt-2`:'ml-3'" v-if="help">{{help}}</span>
<cardlayer :class="{'mt-3':files.length}">
<card class="upload-item" v-for="(item,idx) in files" :key="idx" :style="`flex: 0 0 ${1/col*100}%`">
<cardbody>
<div class="upload-cancel">
<div class="icon-mix">
<icon icon="shanchu" @click.native="onCancel(idx)" title="删除"></icon>
<icon icon="reupload" @click.native="upload" v-if="item.stype=='danger'" title="重传"></icon>
</div>
</div>
<div class="upload-preview" :style="{backgroundImage:`url(${item._base64 || item.base64})`}"></div>
<img class="card-img-top" :src="square">
</cardbody>
<cardfooter :class="item.stype!=''&&`bg-${item.stype}`">
<progressbar class="upload-progress" v-bind="item" size="xs"></progressbar>
<div class="d-flex upload-info ">
<div class="text-truncate mb-0 float-left">{{item.name}}</div>
</div>
</cardfooter>
</card>
</cardlayer>
<!--<div class="file-holder">
<div v-for="(item,idx) in files" :key="idx">
<a href="javascript:;" @click="onCancel(idx)">取消</a>
<div class="holder" :style="{backgroundImage:`url(${item.base64})`}">{{ item.name }}</div>
<progressbar v-bind="item" size="xs"></progressbar>
</div>
</div>-->
<button type="button" class="btn btn-ces mt-3" @click="upload" v-if="!autoUpload&&files.length">上传</button>
</div>
</template>
javascript
<script>
/**
* bootstrap 4.x --> components --> uploader
*
* @param {Boolean} multiple 多选
* @param {Number} max 多选最大值
* @param {Array} maxSize 最大尺寸[width,height] ,不限制不传或者传 0
* @param {Array} allowMime 允许上传的 MIME 类型,默认不限制
* @param {String} url 上传URL
* @param {Boolean} autoUpload 是否选择文件后自动上传
* @param {Function} uploadMethod 自定义上传方法,必须返回Promise,成功resolve
* @param {Function|Object} extraParams
* @param {String} help 上传帮助文字
*
* @event remove 删除
* @event error 错误
* @event success 单个上传成功
* @event completed 全部上传成功
*
* @date 2017-10-24
*
* @requires 依赖 axios icon cards
*
* @version v1.0.0 beta
*
* @author www.bsfans.com 戏子 lopo
*
**/
import icon from './icon'
import progressbar from './progress'
import square from '@/assets/square.png'
import {
cardlayer,
card,
cardheader,
cardbody,
cardfooter
} from '@/components/comp/cards'
import axios from 'axios'

const IMAGE_MIME = ['image/png', 'image/jpg', 'image/gif', 'image/jpeg']

const fnTypeBase64 = text => {
const canvas = document.createElement('canvas')
canvas.width = 200
canvas.height = 200
const ctx = canvas.getContext('2d')
ctx.fillStyle = 'rgba(255,255,255,0.8)'
ctx.fillRect(0, 0, 200, 200)
ctx.stroke()
ctx.font = '40px monaco'
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText(text, 100, 100)
return canvas.toDataURL('image/jpeg')
}

export default {
name: 'uploader',
props: {
multiple: Boolean,
max: Number,
maxSize: {
type: Array,
default: () =>
},
allowMime: {
type: Array,
default: () =>
},
col: {
type: Number,
default: 6
},
help: String,
helpblock:{
type:Boolean,
default:false
},
url: String,
autoUpload: {
type: Boolean,
default: false
},
uploadMethod: Function,
extraParams: [Function, Object]
},
data() {
return {
files: ,
refresh: true,
square: square
}
},
components: {
progressbar,
icon,
cardlayer,
card,
cardheader,
cardbody,
cardfooter
},
methods: {
onPicked(files) {
if (this.max > 0 && this.files.length + files.length > this.max) {
this.$emit('error','图片太大')
return false
}
const isImage = type => IMAGE_MIME.findIndex(item => item == type) > -1
this.$emit('picked', files)
Array.from(files).forEach(file => {
const o = {
file,
name: file.name,
filesize: file.size,
type: file.name
.substring(file.name.lastIndexOf('.'))
.toLowerCase()
.replace('.', ''),
stype: '',
value: 0,
base64: '',
width: 0,
height: 0,
isimage: isImage(),
completed: false,
uploading: false
}

if (this.allowMime.length) {
if (this.allowMime.findIndex(item => item == file.type) === -1) {
return this.$emit('error')
}
}

const reader = new FileReader()
reader.onload = e => {
o.base64 = e.target.result
const idx = this.files.findIndex(item => {
return item.base64 == o.base64 && item.name == o.name
})
if (idx === -1) {
if (isImage(file.type)) {
const image = new Image()
image.onload = () => {
o.width = image.width
o.height = image.height
const [maxwidth, maxheight] = this.maxSize
if (maxwidth && maxwidth < o.width) {
this.$emit('error')
} else if (maxheight && maxheight < o.height) {
this.$emit('error')
} else {
this.files.push(o)
this.autoUpload && this.fnUpload(o)
}
}
image.src = o.base64
} else {
o._base64 = fnTypeBase64(o.type)
this.autoUpload && this.fnUpload(o)
this.files.push(o)
}
}
}
reader.onerror = () => {
this.$emit('error')
}
reader.readAsDataURL(file)
})
this.reset()
},
reset() {
this.refresh = false
this.$nextTick(() => {
this.refresh = true
})
},
onPicker(e) {
this.onPicked(e.target.files)
},
onDrop(e) {
this.onPicked(e.dataTransfer.files)
},
onCancel(idx) {
this.files.splice(idx, 1)
this.$emit('remove', idx)
},
upload() {
this.files.filter(item => !item.uploading).forEach(this.fnUpload)
},
clearFile() {
this.files =
},
fnUpload(o) {
o.uploading = true
if (this.uploadMethod) {
const p = this.uploadMethod(o)
if (p.then) {
p.then(res => {
o.stype = 'success'
o.value = 100
o.completed = true
this.$emit('success', res)
})
}
} else {
// console.log('no then')
const formData = new FormData()
formData.append('file', o.file)
const params =
typeof this.extraParams == 'function'
? this.extraParams(o)
: this.extraParams
Object.keys(params).forEach(key => formData.append(key, params[key]))
axios
.post(this.url, formData, {
onUploadProgress(evt) {
o.value = 100 * evt.loaded / evt.total
}
})
.then(
res => {
o.stype = 'success'
o.completed = true
this.$emit('success', res)
},
err => {
o.stype = 'danger'
this.$emit('error')
}
)
}
}
},
computed: {
accept() {
return this.allowMime.length ? this.allowMime.join(',') : ''
},
isCompleted() {
if (this.files.length) {
return this.files.filter(item => !item.completed).length == 0
} else {
return false
}
},
hasSlot() {
return !!this.$slots.default
}
},
watch: {
isCompleted(v) {
v && this.$emit('completed')
}
},
mounted() {
const DRAG_EVENTS = ['dragleave', 'drop', 'dragenter', 'dragover']
DRAG_EVENTS.forEach(evtName => {
document.addEventListener(evtName, e => e.preventDefault(), false)
})
}
}
</script>
style(less)
<style lang="less">
@import (reference) '../../assets/lib/css.less';
.upload {
input[type='file'] {
opacity: 0;
}
.custom-file {
.mgb(1rem);
}
&-progress {
.ps;
top: 0;
left: 0;
right: 0;
}
&-info {
.cp;
align-items: center;
}
/*&-progress {
.ppd;
width:0;
z-index: 2;
.bgc(@ces);
}*/
&-item {
.card-body {
overflow: hidden;
position: relative;
z-index: 1;
}
.card-footer {
.trs;
.pr;
.bgcw;
.pdy(0.3rem);
&[class*='bg'] {
.crw;
}
}
}
&-preview {
.ppd(1.25rem);
.bdr(@cr: rgba(0, 0, 0, 0.1));
background-repeat: no-repeat;
background-position: center center;
background-size: 100%;
}
&-cancel {
.mask;
.trs;
opacity: 0;
.bgc(rgba(0, 0, 0, 0.5));
z-index: 3;
.icon-mix {
.amid;
.iconfont + .iconfont {
.mgl(1.5rem);
}
}
.iconfont {
.fs(1.6rem);
.cp;
.trs;
.trf(scale(0));
.crw;
.trfo(50% 50%);
&:after {
content: '';
.bgc(rgba(0, 0, 0, 0.2));
width: 3rem;
height: 3rem;
.db;
.mask;
.trs;
z-index: -1;
left: -0.7rem;
top: -0.3rem;
.bdrrd(3rem);
}
&:hover:after {
.bgc(@ces);
}
}
.upload .card:hover & {
opacity: 1;
.iconfont {
.trf(scale(1));
}
}
}
}
</style>

QQ图片20180316224810.png

?

vue+bootstrap4+tooltip.js 实现简单的tooltip

lopo1983 发表了文章 • 0 个评论 • 2866 次浏览 • 2018-01-24 17:33 • 来自相关话题

<template lang="pug">
button(:class="`btn btn-${size} btn-${stype}`",ref="button",@mouseover="initPopper")
<slot></slot>
<template v-if="!$slots.default">{{label}}</template>
</template>
<script>
import lib from '@/utils/lib'
import btn from '@/components/comp/button'
import Tooltip from 'tooltip.js'
export default {
name: 'vbaToolTip',
components: {
btn
},
props: {
label: String,
size: String,
stype: String,
placement: {
type: String,
default: 'top'
},
html: {
type: Boolean,
default: false
},
content: String
},
data() {
return {
popperInstance: null
}
},
methods: {
initPopper() {
if (!this.popperInstance) {
const vm = this
this.popperInstance = new Tooltip(this.$refs.button, {
placement: `${this.placement}`,
template: `<div class="tooltip bs-tooltip-${
this.placement
}" role="tooltip">
<div class="tooltip-arrow arrow"></div>
<div class="tooltip-inner">
</div>
</div>`,
title: this.content,
html:this.html,
contaier:document.getElementsByTagName('body'),
onCreate() {
vm.$emit('on-create', this.popperInstance)
},
onUpdate() {
vm.$emit('on-update', this.popperInstance)
}
})
}
}
}
}
</script>
<style lang="less">
.tooltip {
opacity: 1 !important;
}
</style> 查看全部
<template lang="pug">
button(:class="`btn btn-${size} btn-${stype}`",ref="button",@mouseover="initPopper")
<slot></slot>
<template v-if="!$slots.default">{{label}}</template>
</template>
<script>
import lib from '@/utils/lib'
import btn from '@/components/comp/button'
import Tooltip from 'tooltip.js'
export default {
name: 'vbaToolTip',
components: {
btn
},
props: {
label: String,
size: String,
stype: String,
placement: {
type: String,
default: 'top'
},
html: {
type: Boolean,
default: false
},
content: String
},
data() {
return {
popperInstance: null
}
},
methods: {
initPopper() {
if (!this.popperInstance) {
const vm = this
this.popperInstance = new Tooltip(this.$refs.button, {
placement: `${this.placement}`,
template: `<div class="tooltip bs-tooltip-${
this.placement
}" role="tooltip">
<div class="tooltip-arrow arrow"></div>
<div class="tooltip-inner">
</div>
</div>`,
title: this.content,
html:this.html,
contaier:document.getElementsByTagName('body'),
onCreate() {
vm.$emit('on-create', this.popperInstance)
},
onUpdate() {
vm.$emit('on-update', this.popperInstance)
}
})
}
}
}
}
</script>
<style lang="less">
.tooltip {
opacity: 1 !important;
}
</style>

vue2.x 将table 内容导出excel 下载(转换页面table数据)

lopo1983 发表了文章 • 0 个评论 • 2976 次浏览 • 2018-01-22 18:06 • 来自相关话题

目前仅支持chrome?downloadjs需自行引入加载 对应的tab HTMLCollection 也应根据你的UI组件进行替换









<template lang="pug">
btn(:stype="stype",:size="size",@click="exportOffice(tid)")
slot
template(v-if="!$slots.default") {{label}}
</template>

<script>
/**
* bootstrap 4.x --> components --> table-->table
*
* @param {String} label 按钮名称
* @param {String} stype 样式
* @param {String} size 大小
*
* 注意以上参数为内置按钮参数 可根据你目前使用的UI组件进行修改
*
* @param {String} tid 表格ID
* @param {String} xlsname excel名称
* @param {String} filters 需过滤的cell className
*
* @date 2018-1-22
*
* @version v1.0.0 beta
*
* @important 注意目前仅支持chrome下使用
*
**/
import download from 'downloadjs'
import btn from '@/components/comp/button'
export default {
name: 'tableToExcel',
components: {
btn
},
props: {
// btn 组件参数
label: String,
stype: String,
size: {
type: String,
default: 'sm'
},
tid: String,
xlsname:String,
filters: String
},
methods: {
base64ToBlob: function(data, mime) {
let base64 = window.btoa(window.unescape(encodeURIComponent(data)))
let bstr = atob(base64)
let n = bstr.length
let u8arr = new Uint8ClampedArray(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
return new Blob([u8arr], { type: mime })
},
exportOffice(tableID) {
let elms = document.getElementById(tableID).cloneNode(true)
let table = elms.childNodes[1].childNodes[0]
// table根据你的ui组件修改对应的HTMLCollection
let rows = table.rows.length
let cells = table.rows.item(0).cells.length
let tableArr = table.getElementsByClassName(this.filters)
for (let i = 0; i < rows; i++) {
table.rows<em>.deleteCell(cells-1)
}
let excelContent = table.innerHTML
let excelFile = `<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html4 ... Bmeta name=ProgId content=Excel.Sheet> <meta name=Generator content="Microsoft Excel 11"><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet><x:Name>{worksheet}</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]--></head><body><table>${excelContent}</table></body></html>`
let blob = this.base64ToBlob(excelFile, 'application/vnd.ms-excel')
download(blob, this.xlsname||this.$route.name+moment().format('x'), 'application/vnd.ms-excel')
}
}
}
</script> 查看全部

目前仅支持chrome?downloadjs需自行引入加载 对应的tab HTMLCollection 也应根据你的UI组件进行替换



table.png


QQ图片20180122224557.png
<template lang="pug">
btn(:stype="stype",:size="size",@click="exportOffice(tid)")
slot
template(v-if="!$slots.default") {{label}}
</template>

<script>
/**
* bootstrap 4.x --> components --> table-->table
*
* @param {String} label 按钮名称
* @param {String} stype 样式
* @param {String} size 大小
*
* 注意以上参数为内置按钮参数 可根据你目前使用的UI组件进行修改
*
* @param {String} tid 表格ID
* @param {String} xlsname excel名称
* @param {String} filters 需过滤的cell className
*
* @date 2018-1-22
*
* @version v1.0.0 beta
*
* @important 注意目前仅支持chrome下使用
*
**/
import download from 'downloadjs'
import btn from '@/components/comp/button'
export default {
name: 'tableToExcel',
components: {
btn
},
props: {
// btn 组件参数
label: String,
stype: String,
size: {
type: String,
default: 'sm'
},
tid: String,
xlsname:String,
filters: String
},
methods: {
base64ToBlob: function(data, mime) {
let base64 = window.btoa(window.unescape(encodeURIComponent(data)))
let bstr = atob(base64)
let n = bstr.length
let u8arr = new Uint8ClampedArray(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
return new Blob([u8arr], { type: mime })
},
exportOffice(tableID) {
let elms = document.getElementById(tableID).cloneNode(true)
let table = elms.childNodes[1].childNodes[0]
// table根据你的ui组件修改对应的HTMLCollection
let rows = table.rows.length
let cells = table.rows.item(0).cells.length
let tableArr = table.getElementsByClassName(this.filters)
for (let i = 0; i < rows; i++) {
table.rows<em>.deleteCell(cells-1)
}
let excelContent = table.innerHTML
let excelFile = `<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html4 ... Bmeta name=ProgId content=Excel.Sheet> <meta name=Generator content="Microsoft Excel 11"><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet><x:Name>{worksheet}</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]--></head><body><table>${excelContent}</table></body></html>`
let blob = this.base64ToBlob(excelFile, 'application/vnd.ms-excel')
download(blob, this.xlsname||this.$route.name+moment().format('x'), 'application/vnd.ms-excel')
}
}
}
</script>

vue2.x 将table 内容导出excel 下载(直接转换渲染数据)

lopo1983 发表了文章 • 0 个评论 • 2772 次浏览 • 2018-01-22 17:51 • 来自相关话题

https://github.com/jecovier/vue-json-excel
<template lang="pug">
btn(:stype="stype",:size="size",@click="generate")
slot
template(v-if="!$slots.default") {{label}}
</template>
<script>
// https://github.com/jecovier/vue-json-excel
import download from 'downloadjs'
import btn from '@/components/comp/button'
export default {
name: 'vbaJsonExcel',
components: {
btn
},
props: {
// btn 组件参数
label: String,
stype: String,
size: {
type: String,
default: 'sm'
},
// 设置导出格式 [xls, csv], 默认: xls
type: {
type: String,
default: 'xls'
},
// Json to download
data: {
type: Array,
required: true
},
// 导出的表格标题
// 若不设置将使用默认的json数据key
fields: {
type: Object,
required: true
},
// excel标题
title: {
type: String,
default: null
},
// 导出excel标题名称
name: {
type: String,
default: 'datas.xls'
},
meta: {
type: Array,
default: () =>
}
},
computed: {
// unique identifier
idName: function() {
var now = new Date().getTime()
return 'export_' + now
}
},
methods: {
generate() {
if (!this.data.length) {
return
}
let json = this.getProcessedJson(this.data, this.fields)
if (this.type == 'csv') {
return this.export(this.jsonToCSV(json), this.name, 'application/csv')
}
return this.export(
this.jsonToXLS(json),
this.name,
'application/vnd.ms-excel'
)
},
/*
Use downloadjs to generate the download link
*/
export: function(data, filename, mime) {
let blob = this.base64ToBlob(data, mime)
download(blob, filename, mime)
},
/*
jsonToXLS
---------------
Transform json data into an xml document with MS Excel format, sadly
this format show a prompt when open due to a default behavior
on Microsoft office. It's recommended to use CSV format instead.
*/
jsonToXLS: function(data) {
let xlsTemp =
'<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html4 ... Bmeta name=ProgId content=Excel.Sheet> <meta name=Generator content="Microsoft Excel 11"><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet><x:Name>{worksheet}</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]--></head><body><table>${table}</table></body></html>'
let xlsData = '<thead><tr>'

if (this.title != null) {
xlsData +=
'<tr><th colspan="' +
Object.keys(data[0]).length +
'">' +
this.title +
'<th></tr>'
}

for (let key in data[0]) {
xlsData += '<th>' + key + '</th>'
}
xlsData += '</tr></thead>'
xlsData += '<tbody>'

data.map(function(item, index) {
xlsData += '<tbody><tr>'
for (let key in item) {
xlsData += '<td>' + item[key] + '</td>'
}
xlsData += '</tr></tbody>'
})
return xlsTemp.replace('${table}', xlsData)
},
/*
jsonToCSV
---------------
Transform json data into an CSV file.
*/
jsonToCSV: function(data) {
var csvData = ''

if (this.title != null) {
csvData += this.title + '\r\n'
}

for (let key in data[0]) {
csvData += key + ','
}
csvData = csvData.slice(0, csvData.length - 1)
csvData += '\r\n'

data.map(function(item) {
for (let key in item) {
let escapedCSV = item[key] + '' // cast Numbers to string
if (escapedCSV.match(/[,"\n]/)) {
escapedCSV = '"' + escapedCSV.replace(/\"/g, '""') + '"'
}
csvData += escapedCSV + ','
}
csvData = csvData.slice(0, csvData.length - 1)
csvData += '\r\n'
})
return csvData
},
/*
getProcessedJson
---------------
Get only the data to export, if no fields are set return all the data
*/
getProcessedJson: function(data, header) {
let keys = this.getKeys(data, header)
let newData =
let _self = this
data.map(function(item, index) {
let newItem = {}
for (let label in keys) {
var iii = item
let property = keys[label]
newItem[label] = _self.getNestedData(property, item)
}
newData.push(newItem)
})

return newData
},
getKeys: function(data, header) {
if (header) {
return header
}

let keys = {}
for (let key in data[0]) {
keys[key] = key
}
return keys
},
getNestedData: function(key, item) {
let valueFromNestedKey = null
let keyNestedSplit = key.split('.')
valueFromNestedKey = item[keyNestedSplit[0]]
for (let j = 1; j < keyNestedSplit.length; j++) {
valueFromNestedKey = valueFromNestedKey[keyNestedSplit[j]]
}
return valueFromNestedKey
},
base64ToBlob: function(data, mime) {
let base64 = window.btoa(window.unescape(encodeURIComponent(data)))
let bstr = atob(base64)
let n = bstr.length
let u8arr = new Uint8ClampedArray(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
return new Blob([u8arr], { type: mime })
}
} // end methods
}
</script>


fileds 获取(该组件需要对标题进行格式化)
?
methods
setFields() {
let _this = this
let Obj = new Object()
let arr = this.$refs.tables.$slots.default
.filter(e => e.componentInstance)
.map((column, index) => {
const instance = column.componentOptions.propsData
return instance
})
arr.forEach(element => {
if (!!element['prop']) {
_this.xlsfields[element['label']] = element['prop']
}
})
}mounted
mounted() {
this.setFields()
} 查看全部

https://github.com/jecovier/vue-json-excel


<template lang="pug">
btn(:stype="stype",:size="size",@click="generate")
slot
template(v-if="!$slots.default") {{label}}
</template>
<script>
// https://github.com/jecovier/vue-json-excel
import download from 'downloadjs'
import btn from '@/components/comp/button'
export default {
name: 'vbaJsonExcel',
components: {
btn
},
props: {
// btn 组件参数
label: String,
stype: String,
size: {
type: String,
default: 'sm'
},
// 设置导出格式 [xls, csv], 默认: xls
type: {
type: String,
default: 'xls'
},
// Json to download
data: {
type: Array,
required: true
},
// 导出的表格标题
// 若不设置将使用默认的json数据key
fields: {
type: Object,
required: true
},
// excel标题
title: {
type: String,
default: null
},
// 导出excel标题名称
name: {
type: String,
default: 'datas.xls'
},
meta: {
type: Array,
default: () =>
}
},
computed: {
// unique identifier
idName: function() {
var now = new Date().getTime()
return 'export_' + now
}
},
methods: {
generate() {
if (!this.data.length) {
return
}
let json = this.getProcessedJson(this.data, this.fields)
if (this.type == 'csv') {
return this.export(this.jsonToCSV(json), this.name, 'application/csv')
}
return this.export(
this.jsonToXLS(json),
this.name,
'application/vnd.ms-excel'
)
},
/*
Use downloadjs to generate the download link
*/
export: function(data, filename, mime) {
let blob = this.base64ToBlob(data, mime)
download(blob, filename, mime)
},
/*
jsonToXLS
---------------
Transform json data into an xml document with MS Excel format, sadly
this format show a prompt when open due to a default behavior
on Microsoft office. It's recommended to use CSV format instead.
*/
jsonToXLS: function(data) {
let xlsTemp =
'<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html4 ... Bmeta name=ProgId content=Excel.Sheet> <meta name=Generator content="Microsoft Excel 11"><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet><x:Name>{worksheet}</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]--></head><body><table>${table}</table></body></html>'
let xlsData = '<thead><tr>'

if (this.title != null) {
xlsData +=
'<tr><th colspan="' +
Object.keys(data[0]).length +
'">' +
this.title +
'<th></tr>'
}

for (let key in data[0]) {
xlsData += '<th>' + key + '</th>'
}
xlsData += '</tr></thead>'
xlsData += '<tbody>'

data.map(function(item, index) {
xlsData += '<tbody><tr>'
for (let key in item) {
xlsData += '<td>' + item[key] + '</td>'
}
xlsData += '</tr></tbody>'
})
return xlsTemp.replace('${table}', xlsData)
},
/*
jsonToCSV
---------------
Transform json data into an CSV file.
*/
jsonToCSV: function(data) {
var csvData = ''

if (this.title != null) {
csvData += this.title + '\r\n'
}

for (let key in data[0]) {
csvData += key + ','
}
csvData = csvData.slice(0, csvData.length - 1)
csvData += '\r\n'

data.map(function(item) {
for (let key in item) {
let escapedCSV = item[key] + '' // cast Numbers to string
if (escapedCSV.match(/[,"\n]/)) {
escapedCSV = '"' + escapedCSV.replace(/\"/g, '""') + '"'
}
csvData += escapedCSV + ','
}
csvData = csvData.slice(0, csvData.length - 1)
csvData += '\r\n'
})
return csvData
},
/*
getProcessedJson
---------------
Get only the data to export, if no fields are set return all the data
*/
getProcessedJson: function(data, header) {
let keys = this.getKeys(data, header)
let newData =
let _self = this
data.map(function(item, index) {
let newItem = {}
for (let label in keys) {
var iii = item
let property = keys[label]
newItem[label] = _self.getNestedData(property, item)
}
newData.push(newItem)
})

return newData
},
getKeys: function(data, header) {
if (header) {
return header
}

let keys = {}
for (let key in data[0]) {
keys[key] = key
}
return keys
},
getNestedData: function(key, item) {
let valueFromNestedKey = null
let keyNestedSplit = key.split('.')
valueFromNestedKey = item[keyNestedSplit[0]]
for (let j = 1; j < keyNestedSplit.length; j++) {
valueFromNestedKey = valueFromNestedKey[keyNestedSplit[j]]
}
return valueFromNestedKey
},
base64ToBlob: function(data, mime) {
let base64 = window.btoa(window.unescape(encodeURIComponent(data)))
let bstr = atob(base64)
let n = bstr.length
let u8arr = new Uint8ClampedArray(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
return new Blob([u8arr], { type: mime })
}
} // end methods
}
</script>


fileds 获取(该组件需要对标题进行格式化)
?
methods
    setFields() {
let _this = this
let Obj = new Object()
let arr = this.$refs.tables.$slots.default
.filter(e => e.componentInstance)
.map((column, index) => {
const instance = column.componentOptions.propsData
return instance
})
arr.forEach(element => {
if (!!element['prop']) {
_this.xlsfields[element['label']] = element['prop']
}
})
}
mounted
mounted() {
this.setFields()
}

vue-cli 模拟数据配置

戏子 发表了文章 • 0 个评论 • 1937 次浏览 • 2018-01-16 11:13 • 来自相关话题

build/webpack.dev.conf.js
'use strict'
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const path = require('path')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const portfinder = require('portfinder')

const HOST = process.env.HOST
const PORT = process.env.PORT && Number(process.env.PORT)

const devWebpackConfig = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
},
// cheap-module-eval-source-map is faster for development
devtool: config.dev.devtool,

// these devServer options should be customized in /config/index.js
devServer: {
clientLogLevel: 'warning',
historyApiFallback: {
rewrites: [
{ from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
],
},
hot: true,
contentBase: false, // since we use CopyWebpackPlugin.
compress: true,
host: HOST || config.dev.host,
port: PORT || config.dev.port,
open: config.dev.autoOpenBrowser,
overlay: config.dev.errorOverlay ?
{ warnings: false, errors: true } :
false,
publicPath: config.dev.assetsPublicPath,
proxy: config.dev.proxyTable,
quiet: true, // necessary for FriendlyErrorsPlugin
watchOptions: {
poll: config.dev.poll,
},
before(app) {
app.get('/api/a', (req, res) => {
if (res.json) {
res.json({
errno: 0,
data: 'AA'
})
}
})
app.get('/api/b', (req, res) => {
if (res.json) {
res.json({
errno: 0,
data: 'BB'
})
}
})
}
},
plugins: [
new webpack.DefinePlugin({
'process.env': require('../config/dev.env')
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
new webpack.NoEmitOnErrorsPlugin(),
// https://github.com/ampedandwir ... lugin
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
}),
// copy custom static assets
new CopyWebpackPlugin([{
from: path.resolve(__dirname, '../static'),
to: config.dev.assetsSubDirectory,
ignore: ['.*']
}])
]
})

module.exports = new Promise((resolve, reject) => {
portfinder.basePort = process.env.PORT || config.dev.port
portfinder.getPort((err, port) => {
if (err) {
reject(err)
} else {
// publish the new Port, necessary for e2e tests
process.env.PORT = port
// add port to devServer config
devWebpackConfig.devServer.port = port

// Add FriendlyErrorsPlugin
devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
compilationSuccessInfo: {
messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
},
onErrors: config.dev.notifyOnErrors ?
utils.createNotifierCallback() :
undefined
}))

resolve(devWebpackConfig)
}
})
}) 查看全部
build/webpack.dev.conf.js
'use strict'
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const path = require('path')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const portfinder = require('portfinder')

const HOST = process.env.HOST
const PORT = process.env.PORT && Number(process.env.PORT)

const devWebpackConfig = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
},
// cheap-module-eval-source-map is faster for development
devtool: config.dev.devtool,

// these devServer options should be customized in /config/index.js
devServer: {
clientLogLevel: 'warning',
historyApiFallback: {
rewrites: [
{ from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
],
},
hot: true,
contentBase: false, // since we use CopyWebpackPlugin.
compress: true,
host: HOST || config.dev.host,
port: PORT || config.dev.port,
open: config.dev.autoOpenBrowser,
overlay: config.dev.errorOverlay ?
{ warnings: false, errors: true } :
false,
publicPath: config.dev.assetsPublicPath,
proxy: config.dev.proxyTable,
quiet: true, // necessary for FriendlyErrorsPlugin
watchOptions: {
poll: config.dev.poll,
},
before(app) {
app.get('/api/a', (req, res) => {
if (res.json) {
res.json({
errno: 0,
data: 'AA'
})
}
})
app.get('/api/b', (req, res) => {
if (res.json) {
res.json({
errno: 0,
data: 'BB'
})
}
})
}
},
plugins: [
new webpack.DefinePlugin({
'process.env': require('../config/dev.env')
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
new webpack.NoEmitOnErrorsPlugin(),
// https://github.com/ampedandwir ... lugin
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
}),
// copy custom static assets
new CopyWebpackPlugin([{
from: path.resolve(__dirname, '../static'),
to: config.dev.assetsSubDirectory,
ignore: ['.*']
}])
]
})

module.exports = new Promise((resolve, reject) => {
portfinder.basePort = process.env.PORT || config.dev.port
portfinder.getPort((err, port) => {
if (err) {
reject(err)
} else {
// publish the new Port, necessary for e2e tests
process.env.PORT = port
// add port to devServer config
devWebpackConfig.devServer.port = port

// Add FriendlyErrorsPlugin
devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
compilationSuccessInfo: {
messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
},
onErrors: config.dev.notifyOnErrors ?
utils.createNotifierCallback() :
undefined
}))

resolve(devWebpackConfig)
}
})
})

axios RESTful 接口封装....(进一步优化版本)

lopo1983 发表了文章 • 0 个评论 • 4351 次浏览 • 2018-01-15 04:23 • 来自相关话题

import axios from 'axios'
import { baseUrl, localUrl } from '@/config/env'
import Vue from 'vue'
import vuex from 'vuex'
import store from '../store'
const instance = axios.create()
instance.defaults.baseURL = baseUrl
instance.interceptors.request.use(
function(config) {
if (Lockr.get('token')) {
config.headers.common['Authorization'] = Lockr.get('token')
}
return config
},
error => {
return Promise.reject(error)
}
)
instance.interceptors.response.use(
res => {
if (res.data.code) {
switch (res.data.code) {
case 401:
Lockr.rm('token')
window.location.href = '/#/admin/login/'
break
}
} else {
}
return res
},
err => {
if (err && err.response) {
switch (err.response.status) {
case 400:
err.message = '请求错误'
break
case 401:
err.message = '授权失败,请检查token'
break
case 403:
err.message = '拒绝访问'
break
case 404:
err.message = `请求${err.response.config.url
.split('/')
.pop()
.replace(/\.html/, '')}接口出错`
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:
}
}
store.commit('setModal', err.message)
return Promise.reject(err)
}
)
export function ajax(url, params, type) {
let config = { method: type || 'post' }
if (type === 'get') {
config.params = params
} else if (type === 'put' || type === 'patch' || type === 'delete') {
config.data = type ? {} : params
config.headers = { 'Content-Type': 'application/x-www-form-urlencoded' }
config.transformRequest = [
() => {
let ret = new URLSearchParams()
for (let key in params) {
ret.append(key, params[key])
}
return ret
}
]
} else {
config.data = type ? {} : params
config.headers = { 'Content-Type': 'multipart/form-data' }
config.transformRequest = [
() => {
if (!_.isPlainObject(params)) {
if (params instanceof FormData) {
return params
} else {
let ret = new FormData(params)
return ret
}
} else {
let ret = new FormData()
for (var key in params) {
ret.append(key, params[key])
}
return ret
}
}
]
}
return instance(url, config).then(response => {
return response.data
})
} 查看全部
import axios from 'axios'
import { baseUrl, localUrl } from '@/config/env'
import Vue from 'vue'
import vuex from 'vuex'
import store from '../store'
const instance = axios.create()
instance.defaults.baseURL = baseUrl
instance.interceptors.request.use(
function(config) {
if (Lockr.get('token')) {
config.headers.common['Authorization'] = Lockr.get('token')
}
return config
},
error => {
return Promise.reject(error)
}
)
instance.interceptors.response.use(
res => {
if (res.data.code) {
switch (res.data.code) {
case 401:
Lockr.rm('token')
window.location.href = '/#/admin/login/'
break
}
} else {
}
return res
},
err => {
if (err && err.response) {
switch (err.response.status) {
case 400:
err.message = '请求错误'
break
case 401:
err.message = '授权失败,请检查token'
break
case 403:
err.message = '拒绝访问'
break
case 404:
err.message = `请求${err.response.config.url
.split('/')
.pop()
.replace(/\.html/, '')}接口出错`
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:
}
}
store.commit('setModal', err.message)
return Promise.reject(err)
}
)
export function ajax(url, params, type) {
let config = { method: type || 'post' }
if (type === 'get') {
config.params = params
} else if (type === 'put' || type === 'patch' || type === 'delete') {
config.data = type ? {} : params
config.headers = { 'Content-Type': 'application/x-www-form-urlencoded' }
config.transformRequest = [
() => {
let ret = new URLSearchParams()
for (let key in params) {
ret.append(key, params[key])
}
return ret
}
]
} else {
config.data = type ? {} : params
config.headers = { 'Content-Type': 'multipart/form-data' }
config.transformRequest = [
() => {
if (!_.isPlainObject(params)) {
if (params instanceof FormData) {
return params
} else {
let ret = new FormData(params)
return ret
}
} else {
let ret = new FormData()
for (var key in params) {
ret.append(key, params[key])
}
return ret
}
}
]
}
return instance(url, config).then(response => {
return response.data
})
}

RESTful 标准的axios封装(vue2.x)

lopo1983 发表了文章 • 0 个评论 • 2280 次浏览 • 2018-01-04 16:54 • 来自相关话题

export function ajax(url, params, type) {
let config = { method: type || 'post' }
if (type === 'get') {
config.params = params
} else if (type === 'put' || type === 'patch' || type === 'delete') {
config.data = type ? {} : params
config.headers = { 'Content-Type': 'application/x-www-form-urlencoded' }
config.transformRequest = [
() => {
let ret = new URLSearchParams()
for (let key in params) {
ret.append(key, params[key])
}
return ret
}
]
} else {
config.data = type ? {} : params
}
return instance(url, config).then(response => {
return response.data
})
}?
?
instance.interceptors.response.use(
res => {
if (res.data.code) {
switch (res.data.code) {
case 401:
Lockr.rm('token')
window.location.href = '/#/admin/login/'
break
}
} else {
}
return res
},
err => {
if (err && err.response) {
switch (err.response.status) {
case 400:
err.message = '请求错误'
break
case 401:
err.message = '授权失败,请检查token'
break
case 403:
err.message = '拒绝访问'
break
case 404:
err.message = `请求${err.response.config.url
.split('/')
.pop()
.replace(/\.html/, '')}接口出错`
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:
}
}
store.commit('setModal', err.message)
return Promise.reject(err)
}
) 查看全部
export function ajax(url, params, type) {
let config = { method: type || 'post' }
if (type === 'get') {
config.params = params
} else if (type === 'put' || type === 'patch' || type === 'delete') {
config.data = type ? {} : params
config.headers = { 'Content-Type': 'application/x-www-form-urlencoded' }
config.transformRequest = [
() => {
let ret = new URLSearchParams()
for (let key in params) {
ret.append(key, params[key])
}
return ret
}
]
} else {
config.data = type ? {} : params
}
return instance(url, config).then(response => {
return response.data
})
}
?
?
instance.interceptors.response.use(
res => {
if (res.data.code) {
switch (res.data.code) {
case 401:
Lockr.rm('token')
window.location.href = '/#/admin/login/'
break
}
} else {
}
return res
},
err => {
if (err && err.response) {
switch (err.response.status) {
case 400:
err.message = '请求错误'
break
case 401:
err.message = '授权失败,请检查token'
break
case 403:
err.message = '拒绝访问'
break
case 404:
err.message = `请求${err.response.config.url
.split('/')
.pop()
.replace(/\.html/, '')}接口出错`
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:
}
}
store.commit('setModal', err.message)
return Promise.reject(err)
}
)

Vue axios 封装 拦截示例

lopo1983 发表了文章 • 0 个评论 • 2294 次浏览 • 2018-01-03 04:42 • 来自相关话题

?
封装export function ajax(url, params, type) {
let config = { method: type || 'post' }
if (type === 'GET') {
config.params = params
} else {
config.data = type ? {} : params
}
console.log(config)
return instance(url, config)
.then(response => {
return response.data
})
.catch(error => {
console.log('通信失败,请检查您的网络,或联系系统管理员')
})
}


?REQ拦截instance.interceptors.request.use(
function(config) {
if (Lockr.get('token')) {
config.headers.common['Authorization'] = Lockr.get('token')
}
return config
},
error => {
return Promise.reject(error)
}
)


RES拦截instance.interceptors.response.use(res => {
if (res.data.code) {
switch (res.data.code) {
case 401:
Lockr.rm('token')
window.location.href = '/#/admin/login/'
break
}
} else {
}
return res
})


?
使用FormData传递数据const instance = axios.create()
instance.defaults.headers.post['Content-Type'] = 'multipart/form-data'
instance.defaults.headers.put['Content-Type'] = 'multipart/form-data'
instance.defaults.headers.patch['Content-Type'] = 'multipart/form-data'



?使用接口
export default {
getList(display = '7', page = '1', db, type = '1') {
let params = {
'display':display,
'current':page,
'type':type
}
return ajax(db, params,'GET')
},
//
putList(db,id) {
let data = new FormData()
data.append('id',id)
return ajax(db, data,'PATCH')
}}


? 查看全部
?
封装
export function ajax(url, params, type) {
let config = { method: type || 'post' }
if (type === 'GET') {
config.params = params
} else {
config.data = type ? {} : params
}
console.log(config)
return instance(url, config)
.then(response => {
return response.data
})
.catch(error => {
console.log('通信失败,请检查您的网络,或联系系统管理员')
})
}


?REQ拦截
instance.interceptors.request.use(
function(config) {
if (Lockr.get('token')) {
config.headers.common['Authorization'] = Lockr.get('token')
}
return config
},
error => {
return Promise.reject(error)
}
)


RES拦截
instance.interceptors.response.use(res => {
if (res.data.code) {
switch (res.data.code) {
case 401:
Lockr.rm('token')
window.location.href = '/#/admin/login/'
break
}
} else {
}
return res
})


?
使用FormData传递数据
const instance = axios.create()
instance.defaults.headers.post['Content-Type'] = 'multipart/form-data'
instance.defaults.headers.put['Content-Type'] = 'multipart/form-data'
instance.defaults.headers.patch['Content-Type'] = 'multipart/form-data'



?使用接口

export default {
getList(display = '7', page = '1', db, type = '1') {
let params = {
'display':display,
'current':page,
'type':type
}
return ajax(db, params,'GET')
},
//
putList(db,id) {
let data = new FormData()
data.append('id',id)
return ajax(db, data,'PATCH')
}}


?

vue-form + bootstrap4 beta 二次封装

lopo1983 发表了文章 • 1 个评论 • 2055 次浏览 • 2017-12-22 10:54 • 来自相关话题

Form 外层组件<template>
<vue-form v-if="!unform" :class="{'form-inline':inline}" :state="state" v-model="state" @submit.prevent="formMethod" >
<slot></slot>
</vue-form>
<div class="form" v-else>
<slot></slot>
</div>
</template>
<script>
export default {
name: 'vbaForm',
provide() {
return {
vbaForm: this
}
},
props: {
inline: Boolean,
unform: {
type: Boolean,
default: false
},
state: {},
formMethod: Function
},
methods: {
fieldClassName(field) {
if (!field) {
return ''
}
if ((field.$touched || field.$submitted) && field.$valid) {
return 'has-success'
}
if ((field.$touched || field.$submitted) && field.$invalid) {
return 'has-error'
}
}
}
}
</script>
<style lang="less">
@import (reference) '../../../assets/lib/css.less';
form,
div {
.form-group:last-child {
.mgb(0);
}
}
</style>


FormItem<template lang="pug">
validate.form-group.required-field.row.mb-4(v-if="!!isForm",auto-label,:class='vbaForm.fieldClassName(`${$parent.formstate}[${prop}]`)')
label.col.col-form-label(:class="{'text-right':right}",v-if="!!col||col===12") {{label}}
cols(:col="col",:size="size")
slot
field-messages.help-block.text-danger.position-absolute.mb-0(:name='prop', show='$touched || $submitted',tag="section")
div(v-for="(item,index) in messages",:slot="item.slot") {{item.label}}
rows.form-group(v-else)
label.col.col-form-label(:class="{'text-right':right}",v-if="!!col||col===12") {{label}}
cols.d-flex.align-items-center(:col="col",:size="size")
slot
</template>
<script>
import { rows, cols } from '@/components/layout'
export default {
name: 'formItem',
components: {
rows,
cols
},
inject: ['vbaForm'],
model:{
prop:'messages'
},
props: {
messages:[Array],
label: String,
right: {
type: Boolean,
default: false
},
col: [Number, Array, String],
size: [String, Array],
prop: String,
unForm:{
type:Boolean,
default:false
}
},
data() {
return {}
},
computed: {
isForm() {
return !this.unForm?!this.vbaForm.unform && this.prop:true
}
}
}
</script>


使用案例方法<template lang="pug">
section#authentication.uc-panel
.d-flex.mt-4
step.w-50.mx-auto(:names="stepData",:current="step")
.d-flex.w-500.mx-auto.my-3
panel.w-100(:stype="step===3?2:3")
Forms(:state="formstate",:formMethod="sendPassword",ref="sendinfo",v-if="step!=3")
template(v-if="step===1")
FormItem(:label="getType==='phone'?'您的手机:':'您的邮箱'",right,:col="9",:unForm="true")
.input-group
input.form-control-plaintext(type="text",readonly,:name="getType",:value="getType==='phone'?$store.state.user.user.phone:$store.state.user.user.email")
span.input-group-btn
timer.btn.btn-sm.btn-secondary(:begin="timer",:second="time",@clevent='getSmsCode')
FormItem(label="您的验证码:",:col="9",right,prop="code",key="code",v-model="rules.code")
input.form-control(type='text',name='code',autocomplete="off",required,v-model.lazy='sendModal.code')
FormItem(:col="9")
button.btn.btn-sm.btn-info.px-3(type="submit") 下一步
template(v-else-if="step===2")
FormItem(label="旧密码:",:col="9",right,prop="oldpassword",key="oldpassword",v-model="rules.oldpassword")
input.form-control(type='password',name='oldpassword',required,v-model.lazy='sendModal.oldpassword')
|
FormItem(label="您的新密码:",:col="9",right,prop="password",key="password",v-model="rules.password")
input.form-control(type='password',password-strength,name='password',required,v-model.lazy='sendModal.password')
|
FormItem(label="重复新密码:",:col="9",right,prop="confirmPassword",key="confirmPassword",v-model="rules.confirmPassword")
input.form-control(type='password',:matches="sendModal.password",name='confirmPassword',required,v-model.lazy='sendModal.confirmPassword')
|
FormItem(:col="9")
button.btn.btn-sm.btn-info.px-3(type="submit") 下一步
</template>
<script>
const sendef = {
code: '',
oldpassword: '',
password: '',
confirmPassword: ''
}
import { card, cardbody } from '@/components/comp/cards'
import step from '@/components/comp/step'
import icons from '@/components/comp/icon'
import panel from '@/view/layout/panel'
import status from '@/components/comp/status'
import timer from '@/components/plug/timer'
import { form, formItem } from '@/components/comp/form'
export default {
name: 'viewAuth',
components: {
step,
panel,
status,
icons,
Forms: form,
FormItem: formItem,
card,
cardbody,
timer
},
data() {
return {
stepData: ['身份验证', '修改密码', '完成'],
step: 1,
time: 120,
timer: false,
formstate: {},
sendModal: _.cloneDeep(sendef),
rules:{
code:[{
slot:'required',
label:'请输入您收到的验证码'
}],
oldpassword:[{
slot:'required',
label:'请输入您的旧密码'
}],
password:[{
slot:'required',
label:'请输入您的新密码'
},{
slot:'password-strength',
label:' 密码需包含大小写字母数字,且必须大于8位!'
}],
confirmPassword:[{
slot:'required',
label:'请重复您的新密码'
},{
slot:'matches',
label:'确认密码与上一次输入不匹配'
}]
}
}
},
computed:{
getType(){
return this.$route.query.changeBy
}
},
methods: {
userinfo() {
const userinfo = this.$store.state.user.user
this.sendModal.phone = userinfo.phone
this.sendModal.email = userinfo.email
},
getSmsCode() {
api.getSmsCode(this.userData.phone).then(res => {

})
},
next(e) {
this.step =2
this.formstate._reset();
},
sendPassword(e) {
if (!!this.formstate.$valid) {
if (this.step === 1) {
this.step = 2
this.formstate._reset();
}
}
}
},
mounted() {
this.userinfo()
}
}
</script>
<style lang="less">
#authentication {
.step-round > li > a:after {
top: -34%;
height: 3px;
}
}
</style>














? 查看全部
Form 外层组件
<template>
<vue-form v-if="!unform" :class="{'form-inline':inline}" :state="state" v-model="state" @submit.prevent="formMethod" >
<slot></slot>
</vue-form>
<div class="form" v-else>
<slot></slot>
</div>
</template>
<script>
export default {
name: 'vbaForm',
provide() {
return {
vbaForm: this
}
},
props: {
inline: Boolean,
unform: {
type: Boolean,
default: false
},
state: {},
formMethod: Function
},
methods: {
fieldClassName(field) {
if (!field) {
return ''
}
if ((field.$touched || field.$submitted) && field.$valid) {
return 'has-success'
}
if ((field.$touched || field.$submitted) && field.$invalid) {
return 'has-error'
}
}
}
}
</script>
<style lang="less">
@import (reference) '../../../assets/lib/css.less';
form,
div {
.form-group:last-child {
.mgb(0);
}
}
</style>


FormItem
<template lang="pug">
validate.form-group.required-field.row.mb-4(v-if="!!isForm",auto-label,:class='vbaForm.fieldClassName(`${$parent.formstate}[${prop}]`)')
label.col.col-form-label(:class="{'text-right':right}",v-if="!!col||col===12") {{label}}
cols(:col="col",:size="size")
slot
field-messages.help-block.text-danger.position-absolute.mb-0(:name='prop', show='$touched || $submitted',tag="section")
div(v-for="(item,index) in messages",:slot="item.slot") {{item.label}}
rows.form-group(v-else)
label.col.col-form-label(:class="{'text-right':right}",v-if="!!col||col===12") {{label}}
cols.d-flex.align-items-center(:col="col",:size="size")
slot
</template>
<script>
import { rows, cols } from '@/components/layout'
export default {
name: 'formItem',
components: {
rows,
cols
},
inject: ['vbaForm'],
model:{
prop:'messages'
},
props: {
messages:[Array],
label: String,
right: {
type: Boolean,
default: false
},
col: [Number, Array, String],
size: [String, Array],
prop: String,
unForm:{
type:Boolean,
default:false
}
},
data() {
return {}
},
computed: {
isForm() {
return !this.unForm?!this.vbaForm.unform && this.prop:true
}
}
}
</script>


使用案例方法
<template lang="pug">
section#authentication.uc-panel
.d-flex.mt-4
step.w-50.mx-auto(:names="stepData",:current="step")
.d-flex.w-500.mx-auto.my-3
panel.w-100(:stype="step===3?2:3")
Forms(:state="formstate",:formMethod="sendPassword",ref="sendinfo",v-if="step!=3")
template(v-if="step===1")
FormItem(:label="getType==='phone'?'您的手机:':'您的邮箱'",right,:col="9",:unForm="true")
.input-group
input.form-control-plaintext(type="text",readonly,:name="getType",:value="getType==='phone'?$store.state.user.user.phone:$store.state.user.user.email")
span.input-group-btn
timer.btn.btn-sm.btn-secondary(:begin="timer",:second="time",@clevent='getSmsCode')
FormItem(label="您的验证码:",:col="9",right,prop="code",key="code",v-model="rules.code")
input.form-control(type='text',name='code',autocomplete="off",required,v-model.lazy='sendModal.code')
FormItem(:col="9")
button.btn.btn-sm.btn-info.px-3(type="submit") 下一步
template(v-else-if="step===2")
FormItem(label="旧密码:",:col="9",right,prop="oldpassword",key="oldpassword",v-model="rules.oldpassword")
input.form-control(type='password',name='oldpassword',required,v-model.lazy='sendModal.oldpassword')
|
FormItem(label="您的新密码:",:col="9",right,prop="password",key="password",v-model="rules.password")
input.form-control(type='password',password-strength,name='password',required,v-model.lazy='sendModal.password')
|
FormItem(label="重复新密码:",:col="9",right,prop="confirmPassword",key="confirmPassword",v-model="rules.confirmPassword")
input.form-control(type='password',:matches="sendModal.password",name='confirmPassword',required,v-model.lazy='sendModal.confirmPassword')
|
FormItem(:col="9")
button.btn.btn-sm.btn-info.px-3(type="submit") 下一步
</template>
<script>
const sendef = {
code: '',
oldpassword: '',
password: '',
confirmPassword: ''
}
import { card, cardbody } from '@/components/comp/cards'
import step from '@/components/comp/step'
import icons from '@/components/comp/icon'
import panel from '@/view/layout/panel'
import status from '@/components/comp/status'
import timer from '@/components/plug/timer'
import { form, formItem } from '@/components/comp/form'
export default {
name: 'viewAuth',
components: {
step,
panel,
status,
icons,
Forms: form,
FormItem: formItem,
card,
cardbody,
timer
},
data() {
return {
stepData: ['身份验证', '修改密码', '完成'],
step: 1,
time: 120,
timer: false,
formstate: {},
sendModal: _.cloneDeep(sendef),
rules:{
code:[{
slot:'required',
label:'请输入您收到的验证码'
}],
oldpassword:[{
slot:'required',
label:'请输入您的旧密码'
}],
password:[{
slot:'required',
label:'请输入您的新密码'
},{
slot:'password-strength',
label:' 密码需包含大小写字母数字,且必须大于8位!'
}],
confirmPassword:[{
slot:'required',
label:'请重复您的新密码'
},{
slot:'matches',
label:'确认密码与上一次输入不匹配'
}]
}
}
},
computed:{
getType(){
return this.$route.query.changeBy
}
},
methods: {
userinfo() {
const userinfo = this.$store.state.user.user
this.sendModal.phone = userinfo.phone
this.sendModal.email = userinfo.email
},
getSmsCode() {
api.getSmsCode(this.userData.phone).then(res => {

})
},
next(e) {
this.step =2
this.formstate._reset();
},
sendPassword(e) {
if (!!this.formstate.$valid) {
if (this.step === 1) {
this.step = 2
this.formstate._reset();
}
}
}
},
mounted() {
this.userinfo()
}
}
</script>
<style lang="less">
#authentication {
.step-round > li > a:after {
top: -34%;
height: 3px;
}
}
</style>

QQ图片20171222105832.png


QQ图片20171222105842.png


QQ图片20171222105854.png

?