公共组件库(持续更新)
目录
1 EllipsisTip 组件
2 TreeSelect 组件
3 PicturePreview 组件
创建时间:2022-7-26 14 : 32
更新时间:2022-8-29 19 : 14
1. EllipsisTip 组件
当显示文本太长,省略显示时,可以使用EllipsisTip。该组件是对ToolTip封装,其根据文本所在的容器宽度和文本长度,判断是否截取省略显示文本。
代码 / 示例 在线运行
代码:
12345678910111213141516171819202122232425262728293031323334353637383940<template>
<el-tooltip
:content="text"
:disabled="isTipDisabled"
placement="top"
>
<span class="text" :id="uniqueId">
<slot></slot>
</span>
</el-tooltip>
</template>
<script>
export default {
data() {
return {
isTipDisabled: true,
uniqueId: crypto.randomUUID(),
text: '',
}
},
mounted() {
const el = document.getElementById(this.uniqueId)
if (el) {
this.isTipDisabled = el.scrollWidth <= el.offsetWidth
this.text = el.innerText
}
},
}
</script>
<style lang="scss" scoped>
.text {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
width: 100%;
display: flow-root;
}
</style>
示列:
12345678910111213141516171819202122232425<template>
<div class="text-container">
<ellipsis-tip>{{ text }}</ellipsis-tip>
</div>
</template>
<script>
import EllipsisTip from './EllipsisTip.vue'
export default {
components: {
'ellipsis-tip': EllipsisTip,
},
data() {
return {
text: 'tip-tip-tip-tip-tip-tip-tip-too-long',
};
},
}
</script>
<style scoped>
.text-container {
width: 100px;
}
</style>
说明:
- ellipsis-tip 父容器宽度必须为确定值
框架:
- Vue
- Element-UI
2. TreeSelect 组件
当选择的下拉列表为树型结构时,可以使用TreeSelect。该组件是基于element-ui的el-select和el-tree封装的,不支持多选,不支持树懒加载。
代码 / 示例 在线运行
代码:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131<template>
<el-select
ref="select"
:value="value"
placeholder="请选择"
@visible-change="visibleChange"
@clear="clear"
clearable
style="width: 100%"
>
<el-option class="option">
<el-tree
ref="tree"
class="tree"
:node-key="nodeKey"
:data="data"
:props="props"
:default-expanded-keys='[value]'
highlight-current
:expand-on-click-node="false"
@node-click="handleNodeClick"
>
</el-tree>
</el-option>
</el-select>
</template>
<script>
export default {
name: 'TreeSelect',
// v-model绑定设置
model: {
prop: 'value',
event: 'change',
},
props: {
// v-model绑定属性
value: {
type: [String, Number],
default: ''
},
// 树结构数据
data: {
type: Array,
default: function() {
return []
}
},
// 树节点ID
nodeKey: {
type: [String, Number],
default: 'id'
},
// tree属性配置
props: {
type: Object,
default: function() {
return {
label: 'label',
children: 'children'
}
}
}
},
data() {
return {
}
},
watch: {
value: {
handler(val) {
if (!this.isEmpty(this.data)) {
this.init(val)
}
},
immediate: true
},
data: function(val) {
if (!this.isEmpty(val)) {
this.init(this.value)
}
}
},
methods: {
isEmpty(val) {
for (let key in val) {
return false
}
return true
},
handleNodeClick(data) {
this.$emit('change', data[this.nodeKey])
this.$refs.select.visible = false
},
init(val) {
if (!val) {
this.$refs.tree && this.$refs.tree.setCurrentKey(null)
}
this.$nextTick(() => {
this.$refs.tree.setCurrentKey(val)
})
},
visibleChange(e) {
if (e) {
let selectDom = this.$refs.tree.$el.querySelector('.is-current')
setTimeout(() => {
this.$refs.select.scrollToOption({$el: selectDom})
}, 0)
}
},
clear() {
this.$emit('change', '')
}
}
}
</script>
<style lang="scss" scoped>
.option {
height: auto;
line-height: 1;
padding: 0;
background-color: #fff;
}
.tree {
padding: 4px 20px;
font-weight: 400;
}
</style>
示列:
123456789101112131415161718192021222324252627282930313233343536373839404142<template>
<tree-select
v-model="value"
nodeKey="name"
:data="treeData"
:props="{ label: 'name' }"
>
</tree-select>
</template>
<script>
import TreeSelect from './TreeSelect.vue'
export default {
name: 'HelloWorld',
components: {
'tree-select': TreeSelect,
},
data() {
return {
value: '',
treeData: [
{
id: 1,
name: '根节点',
children: [
{
id: 11,
name: '重庆',
},
{
id: 12,
name: '成都',
},
],
},
],
}
},
}
</script>
<style scoped></style>
框架:
- Vue
- Element-UI
3. PicturePreview 组件
当需要对页面上的图片预览时,可以使用 PicturePreview 组件。该组件支持单张、多张图片预览, 支持缩放、拖拽、旋转、下载功能,并且这些功能可配置化。
代码 / 示例 在线运行
代码:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395<template>
<div class="picture-preview" v-if="visible">
<div
class="preview-img"
target="maintenance-template"
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0,0,0,.3)"
>
<img
v-for="(e, i) in imgUrlList"
v-show="currentIndex === i"
v-zoom="zoom"
v-drag="drag"
:key="e"
:src="e"
:class="['preview-img__img', currentIndex === i ? 'preview-img__img-show' : '']"
:style="imgStyle"
alt="img"
/>
</div>
<el-button class="picture-btn picture-btn__close" icon="el-icon-close" @click="close"></el-button>
<el-button
v-if="imgUrlList.length > 1"
class="picture-btn arrow-left"
icon="el-icon-arrow-left"
@click="prePicture"
:disabled="currentIndex === 0"
></el-button>
<el-button
v-if="imgUrlList.length > 1"
class="picture-btn arrow-right"
icon="el-icon-arrow-right"
@click="nextPicture"
:disabled="currentIndex === imgUrlList.length - 1"
></el-button>
<div class="preview-img__footer-bar" v-if="getHiddenToolBarIcon('all')">
<el-tooltip content="放大">
<i class="el-icon-zoom-in preview-img__footer-bar-icon" @click="zoomIn" v-if="getHiddenToolBarIcon('zoom')"></i>
</el-tooltip>
<el-tooltip content="缩小">
<i
class="el-icon-zoom-out preview-img__footer-bar-icon"
@click="zoomOut"
v-if="getHiddenToolBarIcon('zoom')"
></i>
</el-tooltip>
<el-tooltip content="下载">
<i
class="el-icon-download preview-img__footer-bar-icon"
@click="donwload"
v-if="getHiddenToolBarIcon('download')"
></i>
</el-tooltip>
<el-tooltip content="左旋转">
<i
class="el-icon-refresh-left preview-img__footer-bar-icon"
@click="rotateLeft"
v-if="getHiddenToolBarIcon('rotate')"
></i>
</el-tooltip>
<el-tooltip content="右旋转">
<i
class="el-icon-refresh-right preview-img__footer-bar-icon"
@click="rotateRight"
v-if="getHiddenToolBarIcon('rotate')"
></i>
</el-tooltip>
</div>
</div>
</template>
<script>
const MAX_SCALE_PROPORTION = 5 // 最大放大比列
const MIN_SCALE_PROPORTION = 0.1 // 最小放大比列
const PROPORTION_STEP = 1.2 // 缩放步长
const SCALE_REG = /scale\((.+?)\)/ // 缩放值正则
const ROTATE_REG = /rotate\((.+?)deg\)/ // 旋转角度
export default {
props: {
// 预览弹窗隐显
visible: {
type: Boolean,
default: false,
},
// 预览图片URL
imgUrl: {
type: [Array, String],
default: '',
},
// 图片列表默认预览图片索引
imgIndex: {
type: Number,
default: 0,
},
// 拖动,默认不支持
drag: {
type: Boolean,
default: false,
},
// 滚动缩放,默认不支持
zoom: {
type: Boolean,
default: false,
},
// 隐藏工具栏按钮
hiddenToolBarIcon: {
type: [Array, String],
default: '',
},
},
data() {
return {
currentProportion: 1, // 当前缩放比
currentRotationAngle: 0, // 当前旋转角度
currentIndex: this.imgIndex, // 当前显示图片索引
}
},
directives: {
// 拖拽
drag(el, binding) {
el.onmousedown = function (e) {
if (!binding.value) return
e.preventDefault && e.preventDefault()
const x = e.pageX - el.offsetLeft
const y = e.pageY - el.offsetTop
document.onmousemove = function (e) {
let left = e.clientX - x
let top = e.clientY - y
el.style.left = `${left}px`
el.style.top = `${top}px`
}
document.onmouseup = function (e) {
document.onmousemove = null
document.onmouseup = null
}
}
},
// 缩放
zoom(el, binding, vnode) {
el.onmouseover = function () {
if (!binding.value) return
el.onmousewheel = function (e) {
let proportion = vnode.context.currentProportion || 1
proportion = e.wheelDelta < 0 ? proportion / PROPORTION_STEP : proportion * PROPORTION_STEP
if (proportion > MAX_SCALE_PROPORTION) {
proportion = MAX_SCALE_PROPORTION
} else if (proportion < MIN_SCALE_PROPORTION) {
proportion = MIN_SCALE_PROPORTION
}
// 这里必须和缩放比例同步数据
vnode.context.currentProportion = proportion
el.style.transform = `scale(${proportion})`
}
}
},
},
created() {
// currentIndex 校准
this.setCurrentIndex()
},
computed: {
imgUrlList() {
return Array.isArray(this.imgUrl) ? this.imgUrl : [this.imgUrl]
},
imgStyle() {
return { transform: `scale(${this.currentProportion}) rotate(${this.currentRotationAngle}deg)` }
},
hiddenToolBarIconList() {
return Array.isArray(this.hiddenToolBarIcon) ? this.hiddenToolBarIcon : [this.hiddenToolBarIcon]
},
},
methods: {
// 关闭
close() {
this.$emit('update:visible', false)
// 重置数据
this.currentProportion = 1
this.currentRotationAngle = 0
this.currentIndex = this.imgIndex
this.setCurrentIndex()
},
// 上一张
prePicture() {
if (this.currentIndex !== 0) {
this.currentIndex--
}
},
// 下一张
nextPicture() {
if (this.currentIndex !== this.imgUrl.length - 1) {
this.currentIndex++
}
},
// 放大
zoomIn() {
const zoomTimes = this.getZoomTimes()
if (zoomTimes) {
this.currentProportion = zoomTimes[1] >= MAX_SCALE_PROPORTION ? MAX_SCALE_PROPORTION : zoomTimes[1]
}
this.currentProportion *= PROPORTION_STEP
if (this.currentProportion > MAX_SCALE_PROPORTION) {
this.currentProportion = MAX_SCALE_PROPORTION
}
},
// 缩小
zoomOut() {
const zoomTimes = this.getZoomTimes()
if (zoomTimes) {
this.currentProportion = zoomTimes[1] <= MIN_SCALE_PROPORTION ? MIN_SCALE_PROPORTION : zoomTimes[1]
}
this.currentProportion /= PROPORTION_STEP
if (this.currentProportion < MIN_SCALE_PROPORTION) {
this.currentProportion = MIN_SCALE_PROPORTION
}
},
// 获取缩放比
getZoomTimes() {
const el = document.querySelector('.preview-img__img-show')
const transform = (el.style || {}).transform || ''
return transform.match(SCALE_REG)
},
// 左旋转
rotateLeft() {
const el = document.querySelector('.preview-img__img-show')
const transform = (el.style || {}).transform || ''
let current = transform.match(ROTATE_REG)[1]
current = (+current - 90) % 360
this.currentRotationAngle = current
},
// 右旋转
rotateRight() {
const el = document.querySelector('.preview-img__img-show')
const transform = (el.style || {}).transform || ''
let current = transform.match(ROTATE_REG)[1]
current = +current + 90
this.currentRotationAngle = current
},
// 下载
donwload() {
if (!this.$listeners['download']) {
console.error('No Download Function')
return
}
this.$emit('download', this.currentIndex)
},
// currentIndex 校准
setCurrentIndex() {
if (this.imgIndex < 0) {
this.$nextTick(() => {
this.currentIndex = 0
})
}
const len = this.imgUrlList.length
if (this.imgIndex > len - 1) {
this.$nextTick(() => {
this.currentIndex = len - 1
})
}
},
// 隐藏工具栏图标
getHiddenToolBarIcon(name) {
const list = this.hiddenToolBarIconList
switch (name) {
case 'all':
return !(
list.includes('all') ||
(list.includes('zoom') && list.includes('rotate') && list.includes('download'))
)
case 'zoom':
return !list.includes('zoom')
case 'rotate':
return !list.includes('rotate')
case 'download':
return !list.includes('download')
default:
return false
}
},
},
}
</script>
<style scoped>
.picture-preview {
position: fixed;
overflow: hidden;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(128, 128, 128, 0.4);
z-index: 4;
}
.picture-btn {
width: 40px;
height: 40px;
padding: 0;
background: #585858;
color: #fff;
border-radius: 50%;
z-index: 9;
}
.arrow-left {
position: absolute;
margin: auto;
top: 50%;
left: 120px;
transform: translate(-50%, -50%);
}
.arrow-right {
position: absolute;
margin: auto;
top: 50%;
right: 120px;
transform: translate(-50%, -50%);
}
.el-button--default.is-disabled {
background: #bfbfbf !important;
color: #fff !important;
}
.img-loading {
margin: auto;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 10000;
}
body.theme-dark .picture-btn {
background: #585858;
}
body.theme-dark .picture-btn:is(:link, :visited, :hover, :active, :focus) {
background: #333;
border-color: #333;
}
.picture-btn:is(:link, :visited, :hover, :active, :focus) {
background: #333;
border-color: #333;
color: #fff;
}
.picture-btn__close {
position: absolute;
top: 50px;
right: 50px;
}
.operation-btn-box {
position: absolute;
display: flex;
bottom: 80px;
left: calc(50% - 100px);
justify-content: center;
width: 200px;
}
.preview-img {
position: absolute;
top: 50%;
left: 50%;
width: 900px;
height: 500px;
transform: translate(-50%, -50%);
z-index: 6;
}
.preview-img__img {
position: fixed;
width: 100%;
height: 100%;
object-fit: contain;
left: 0px;
top: 0px;
}
.preview-img__img:active {
cursor: move;
}
.preview-img__footer-bar {
background: #585858;
position: absolute;
bottom: 43px;
transform: translate(-50%);
margin-left: 50%;
height: 38px;
border-radius: 20px;
padding: 4px 10px;
box-sizing: border-box;
z-index: 100;
}
.preview-img__footer-bar-icon {
color: #fff;
font-size: 20px;
margin: 0px 5px;
line-height: 30px;
cursor: pointer;
}
</style>
示列:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162<template>
<div class="container">
<span @click="show" class="title">预览图片</span>
<picture-preview
:visible.sync="visible"
:imgUrl="imgUrl"
:imgIndex="imgIndex"
:hiddenToolBarIcon="''"
@download="download"
drag
zoom
>
</picture-preview>
</div>
</template>
<script>
import axios from 'axios'
import PicturePreview from './components/PicturePreview.vue'
export default {
components: { 'picture-preview': PicturePreview },
data() {
return {
visible: false,
imgUrl: [
'https://image.vblogs.cn/d42285f476421f1ee5dfd282a5c78f1c.jpg',
'https://image.vblogs.cn/fc221c7f0c9b1912b569661dabcafb3c.jpg',
'https://image.vblogs.cn/fdeda65352d448cd8515f3efe3ce47b5.jpg',
],
// imgUrl: 'https://image.vblogs.cn/d42285f476421f1ee5dfd282a5c78f1c.jpg',
imgIndex: 0,
}
},
methods: {
show() {
this.visible = true
},
close() {
this.visible = false
},
download(index) {
console.log('下载 ---- index ----', index)
},
},
}
</script>
<style scoped>
.container {
width: 100%;
padding: 24px;
}
.text-container {
width: 200px;
display: block;
margin-bottom: 16px;
}
.title {
color: #1993ff;
cursor: pointer;
}
</style>
说明:
- ellipsis-tip 父容器宽度必须为确定值
框架:
- Vue
- Element-UI
属性 / 事件
属性
参数 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
visible | 是否显示,支持 .sync 修饰符 | boolean | -- | false |
imgUrl | 图片url | Array | String | -- | -- |
imgIndex | 首次图片预览时的图片索引 | number | -- | 0 |
hiddenToolBarIcon | 隐藏工具栏图标 | Array | String | all | rotate | zoom | download | -- |
drag | 拖动 | boolean | -- | false |
zoom | 鼠标滚动缩放 | boolean | -- | false |
事件
参数 | 说明 | 回调参数 |
---|---|---|
download | 点击下载时的回调 | index: 当前预览图片的索引 |
观察者模式与订阅发布模式
手写 Collapse 折叠面板
文章22
分类11
标签5
博客之家
一个优雅的写作平台
一个优雅的写作平台