Bootstrap

Bootstrap

bootstrap4.x+vue2.x 文件上传

VUElopo1983 发表了文章 • 0 个评论 • 1972 次浏览 • 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

?

关于 bootstrap <media object>变迁

CSS/SASS/SCSS/LESSlopo1983 发表了文章 • 0 个评论 • 1774 次浏览 • 2018-02-05 10:16 • 来自相关话题

media 组件是一个很常用的bootstrap组件 官方的用途解释为:
?
这是一个抽象的样式,用以构建不同类型的组件,这些组件都具有在文本内容的左或右侧对齐的图片(就像博客评论或 Twitter 消息等高度重复的组件
?3.x 的html结构如下:
3.x布局方式 使用的是 display table-cell
<div class="media">
<div class="media-left media-middle">
<a href="#">
<img class="media-object" src="..." alt="...">
</a>
</div>
<div class="media-body">
<h4 class="media-heading">Middle aligned media</h4>
...
</div>
</div><ul class="media-list">
<li class="media">
<div class="media-left">
<a href="#">
<img class="media-object" src="..." alt="...">
</a>
</div>
<div class="media-body">
<h4 class="media-heading">Media heading</h4>
...
</div>
</li>
</ul>3.x 传送门??点我
?
4.x 的结构有些变化 变得更灵活 也更高端(IE10+)
4.x使用 display flex 布局
<div class="media">
<img class="align-self-center mr-3" src="..." alt="Generic placeholder image">
<div class="media-body">
<h5 class="mt-0">Center-aligned media</h5>
<p>Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin. Cras purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc ac nisi vulputate fringilla. Donec lacinia congue felis in faucibus.</p>
<p class="mb-0">Donec sed odio dui. Nullam quis risus eget urna mollis ornare vel eu leo. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p>
</div>
</div><ul class="list-unstyled">
<li class="media">
<img class="mr-3" src="..." alt="Generic placeholder image">
<div class="media-body">
<h5 class="mt-0 mb-1">List-based media object</h5>
Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin. Cras purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc ac nisi vulputate fringilla. Donec lacinia congue felis in faucibus.
</div>
</li>
<li class="media my-4">
<img class="mr-3" src="..." alt="Generic placeholder image">
<div class="media-body">
<h5 class="mt-0 mb-1">List-based media object</h5>
Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin. Cras purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc ac nisi vulputate fringilla. Donec lacinia congue felis in faucibus.
</div>
</li>
<li class="media">
<img class="mr-3" src="..." alt="Generic placeholder image">
<div class="media-body">
<h5 class="mt-0 mb-1">List-based media object</h5>
Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin. Cras purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc ac nisi vulputate fringilla. Donec lacinia congue felis in faucibus.
</div>
</li>
</ul>4.x 传送门 点我
?
关于object 左右的问题 3.x 和4.x 都可以通过对 object流位置进行调整来进行,但4.x还可以通过 设置对应order来直接替换? 关于bootstrap4.x的flex 设置 点我
?使用 display grid 来实现一个media object(grid 传送门 点我)
?
css
.media {
display: grid;
grid-template-rows: repeat(2, auto);
grid-template-columns: 25% calc(~"75% - 1.25rem");
grid-auto-rows: auto;
grid-template-areas: "object content" "object footer";
grid-gap: 0 1.25rem;
.object {
grid-row: 1 / span 2;
grid-column: 1;
}
.content {
grid-row: 1;
grid-column: 2;
}
.footer {
grid-row: 2;
grid-column: 2;
align-self: flex-end;
display: flex;
flex-direction: row;
justify-content: space-between;
}
} 查看全部
media 组件是一个很常用的bootstrap组件 官方的用途解释为:
?

这是一个抽象的样式,用以构建不同类型的组件,这些组件都具有在文本内容的左或右侧对齐的图片(就像博客评论或 Twitter 消息等高度重复的组件


?3.x 的html结构如下:
3.x布局方式 使用的是 display table-cell
<div class="media">
<div class="media-left media-middle">
<a href="#">
<img class="media-object" src="..." alt="...">
</a>
</div>
<div class="media-body">
<h4 class="media-heading">Middle aligned media</h4>
...
</div>
</div>
<ul class="media-list">
<li class="media">
<div class="media-left">
<a href="#">
<img class="media-object" src="..." alt="...">
</a>
</div>
<div class="media-body">
<h4 class="media-heading">Media heading</h4>
...
</div>
</li>
</ul>
3.x 传送门??点我
?
4.x 的结构有些变化 变得更灵活 也更高端(IE10+)
4.x使用 display flex 布局
<div class="media">
<img class="align-self-center mr-3" src="..." alt="Generic placeholder image">
<div class="media-body">
<h5 class="mt-0">Center-aligned media</h5>
<p>Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin. Cras purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc ac nisi vulputate fringilla. Donec lacinia congue felis in faucibus.</p>
<p class="mb-0">Donec sed odio dui. Nullam quis risus eget urna mollis ornare vel eu leo. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p>
</div>
</div>
<ul class="list-unstyled">
<li class="media">
<img class="mr-3" src="..." alt="Generic placeholder image">
<div class="media-body">
<h5 class="mt-0 mb-1">List-based media object</h5>
Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin. Cras purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc ac nisi vulputate fringilla. Donec lacinia congue felis in faucibus.
</div>
</li>
<li class="media my-4">
<img class="mr-3" src="..." alt="Generic placeholder image">
<div class="media-body">
<h5 class="mt-0 mb-1">List-based media object</h5>
Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin. Cras purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc ac nisi vulputate fringilla. Donec lacinia congue felis in faucibus.
</div>
</li>
<li class="media">
<img class="mr-3" src="..." alt="Generic placeholder image">
<div class="media-body">
<h5 class="mt-0 mb-1">List-based media object</h5>
Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin. Cras purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc ac nisi vulputate fringilla. Donec lacinia congue felis in faucibus.
</div>
</li>
</ul>
4.x 传送门 点我
?

关于object 左右的问题 3.x 和4.x 都可以通过对 object流位置进行调整来进行,但4.x还可以通过 设置对应order来直接替换? 关于bootstrap4.x的flex 设置 点我


?使用 display grid 来实现一个media object(grid 传送门 点我
?
css
.media {
display: grid;
grid-template-rows: repeat(2, auto);
grid-template-columns: 25% calc(~"75% - 1.25rem");
grid-auto-rows: auto;
grid-template-areas: "object content" "object footer";
grid-gap: 0 1.25rem;
.object {
grid-row: 1 / span 2;
grid-column: 1;
}
.content {
grid-row: 1;
grid-column: 2;
}
.footer {
grid-row: 2;
grid-column: 2;
align-self: flex-end;
display: flex;
flex-direction: row;
justify-content: space-between;
}
}

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

VUElopo1983 发表了文章 • 0 个评论 • 2652 次浏览 • 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>

bootstrap4.x+vue2.x 文件上传

VUElopo1983 发表了文章 • 0 个评论 • 1972 次浏览 • 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

?

关于 bootstrap <media object>变迁

CSS/SASS/SCSS/LESSlopo1983 发表了文章 • 0 个评论 • 1774 次浏览 • 2018-02-05 10:16 • 来自相关话题

media 组件是一个很常用的bootstrap组件 官方的用途解释为:
?
这是一个抽象的样式,用以构建不同类型的组件,这些组件都具有在文本内容的左或右侧对齐的图片(就像博客评论或 Twitter 消息等高度重复的组件
?3.x 的html结构如下:
3.x布局方式 使用的是 display table-cell
<div class="media">
<div class="media-left media-middle">
<a href="#">
<img class="media-object" src="..." alt="...">
</a>
</div>
<div class="media-body">
<h4 class="media-heading">Middle aligned media</h4>
...
</div>
</div><ul class="media-list">
<li class="media">
<div class="media-left">
<a href="#">
<img class="media-object" src="..." alt="...">
</a>
</div>
<div class="media-body">
<h4 class="media-heading">Media heading</h4>
...
</div>
</li>
</ul>3.x 传送门??点我
?
4.x 的结构有些变化 变得更灵活 也更高端(IE10+)
4.x使用 display flex 布局
<div class="media">
<img class="align-self-center mr-3" src="..." alt="Generic placeholder image">
<div class="media-body">
<h5 class="mt-0">Center-aligned media</h5>
<p>Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin. Cras purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc ac nisi vulputate fringilla. Donec lacinia congue felis in faucibus.</p>
<p class="mb-0">Donec sed odio dui. Nullam quis risus eget urna mollis ornare vel eu leo. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p>
</div>
</div><ul class="list-unstyled">
<li class="media">
<img class="mr-3" src="..." alt="Generic placeholder image">
<div class="media-body">
<h5 class="mt-0 mb-1">List-based media object</h5>
Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin. Cras purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc ac nisi vulputate fringilla. Donec lacinia congue felis in faucibus.
</div>
</li>
<li class="media my-4">
<img class="mr-3" src="..." alt="Generic placeholder image">
<div class="media-body">
<h5 class="mt-0 mb-1">List-based media object</h5>
Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin. Cras purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc ac nisi vulputate fringilla. Donec lacinia congue felis in faucibus.
</div>
</li>
<li class="media">
<img class="mr-3" src="..." alt="Generic placeholder image">
<div class="media-body">
<h5 class="mt-0 mb-1">List-based media object</h5>
Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin. Cras purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc ac nisi vulputate fringilla. Donec lacinia congue felis in faucibus.
</div>
</li>
</ul>4.x 传送门 点我
?
关于object 左右的问题 3.x 和4.x 都可以通过对 object流位置进行调整来进行,但4.x还可以通过 设置对应order来直接替换? 关于bootstrap4.x的flex 设置 点我
?使用 display grid 来实现一个media object(grid 传送门 点我)
?
css
.media {
display: grid;
grid-template-rows: repeat(2, auto);
grid-template-columns: 25% calc(~"75% - 1.25rem");
grid-auto-rows: auto;
grid-template-areas: "object content" "object footer";
grid-gap: 0 1.25rem;
.object {
grid-row: 1 / span 2;
grid-column: 1;
}
.content {
grid-row: 1;
grid-column: 2;
}
.footer {
grid-row: 2;
grid-column: 2;
align-self: flex-end;
display: flex;
flex-direction: row;
justify-content: space-between;
}
} 查看全部
media 组件是一个很常用的bootstrap组件 官方的用途解释为:
?

这是一个抽象的样式,用以构建不同类型的组件,这些组件都具有在文本内容的左或右侧对齐的图片(就像博客评论或 Twitter 消息等高度重复的组件


?3.x 的html结构如下:
3.x布局方式 使用的是 display table-cell
<div class="media">
<div class="media-left media-middle">
<a href="#">
<img class="media-object" src="..." alt="...">
</a>
</div>
<div class="media-body">
<h4 class="media-heading">Middle aligned media</h4>
...
</div>
</div>
<ul class="media-list">
<li class="media">
<div class="media-left">
<a href="#">
<img class="media-object" src="..." alt="...">
</a>
</div>
<div class="media-body">
<h4 class="media-heading">Media heading</h4>
...
</div>
</li>
</ul>
3.x 传送门??点我
?
4.x 的结构有些变化 变得更灵活 也更高端(IE10+)
4.x使用 display flex 布局
<div class="media">
<img class="align-self-center mr-3" src="..." alt="Generic placeholder image">
<div class="media-body">
<h5 class="mt-0">Center-aligned media</h5>
<p>Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin. Cras purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc ac nisi vulputate fringilla. Donec lacinia congue felis in faucibus.</p>
<p class="mb-0">Donec sed odio dui. Nullam quis risus eget urna mollis ornare vel eu leo. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p>
</div>
</div>
<ul class="list-unstyled">
<li class="media">
<img class="mr-3" src="..." alt="Generic placeholder image">
<div class="media-body">
<h5 class="mt-0 mb-1">List-based media object</h5>
Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin. Cras purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc ac nisi vulputate fringilla. Donec lacinia congue felis in faucibus.
</div>
</li>
<li class="media my-4">
<img class="mr-3" src="..." alt="Generic placeholder image">
<div class="media-body">
<h5 class="mt-0 mb-1">List-based media object</h5>
Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin. Cras purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc ac nisi vulputate fringilla. Donec lacinia congue felis in faucibus.
</div>
</li>
<li class="media">
<img class="mr-3" src="..." alt="Generic placeholder image">
<div class="media-body">
<h5 class="mt-0 mb-1">List-based media object</h5>
Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin. Cras purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc ac nisi vulputate fringilla. Donec lacinia congue felis in faucibus.
</div>
</li>
</ul>
4.x 传送门 点我
?

关于object 左右的问题 3.x 和4.x 都可以通过对 object流位置进行调整来进行,但4.x还可以通过 设置对应order来直接替换? 关于bootstrap4.x的flex 设置 点我


?使用 display grid 来实现一个media object(grid 传送门 点我
?
css
.media {
display: grid;
grid-template-rows: repeat(2, auto);
grid-template-columns: 25% calc(~"75% - 1.25rem");
grid-auto-rows: auto;
grid-template-areas: "object content" "object footer";
grid-gap: 0 1.25rem;
.object {
grid-row: 1 / span 2;
grid-column: 1;
}
.content {
grid-row: 1;
grid-column: 2;
}
.footer {
grid-row: 2;
grid-column: 2;
align-self: flex-end;
display: flex;
flex-direction: row;
justify-content: space-between;
}
}

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

VUElopo1983 发表了文章 • 0 个评论 • 2652 次浏览 • 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>