VUE 业务组件库(持续更新)

目录
1 EllipsisTip 组件
2 TreeSelect 组件
3 PicturePreview 组件


创建时间:2022-7-26 14 : 32
更新时间:2022-8-29 19 : 14

1. EllipsisTip 组件

当显示文本太长,省略显示时,可以使用EllipsisTip。该组件是对ToolTip封装,其根据文本所在的容器宽度和文本长度,判断是否截取省略显示文本。

代码 / 示例   在线运行

代码:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
<template> <el-tooltip :content="text" :disabled="isTipDisabled" placement="top"> <span :id="uniqueId" class="text"> <slot></slot> </span> </el-tooltip> </template> <script> function getUUID() { if (typeof crypto === 'object' && typeof crypto.randomUUID === 'function') { return crypto.randomUUID(); } // Math.random() 因安全问题会被扫描出来,使用 LCG // 线性同余伪随机数生成器(LCG),若不涉及安全场景,可以使用 const customRandom = (function() { var m = Math.pow(2, 32); var a = 1103515245; var c = 4258951256; var seed = Date.now(); return function random() { seed = (a * seed + c) % m; return seed / m; } })() return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { const r = (customRandom() * 16) | 0; const v = c === 'x' ? r : (r & 0x3) | 0x8; return v.toString(16); }); } export default { data() { return { isTipDisabled: true, uniqueId: getUUID(), text: '', }; }, mounted() { this.activeToolTip(); // 窗口变化时激活ToolTip window.addEventListener('resize', () => this.activeToolTip()); }, methods: { // 当在弹窗等场景使用时,tooltip可能不生效,此时父组件可以使用ref调用该方法 activeToolTip() { const el = document.getElementById(this.uniqueId); if (el) { this.isTipDisabled = el.scrollWidth <= el.offsetWidth; this.text = el.innerText; } } }, }; </script> <style 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>

说明:

  1. ellipsis-tip 父容器宽度必须为确定值

框架:

  1. Vue
  2. 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>

框架:

  1. Vue
  2. 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>

说明:

  1. ellipsis-tip 父容器宽度必须为确定值

框架:

  1. Vue
  2. 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 折叠面板
评论
luoqiangweb开发China
文章29
分类13
标签7