.
66
BackToTop/README.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# 介绍
|
||||
|
||||
这是一个可以快速回到页面顶部的组件,当用户浏览到页面底部的时候,通过点击按钮,可快速回到页面顶部。
|
||||
|
||||
# 使用方法
|
||||
|
||||
由于该组件是基于`element-UI`进行二次封装的,所以在使用该组件时请务必安装`element-UI`([安装方式猛戳这里](http://element-cn.eleme.io/#/zh-CN/component/installation)),安装好`element-UI`后,只需将该组件文件夹`BackToTop`导入到现有项目中即可使用。
|
||||
|
||||
# 使用示例
|
||||
|
||||
```html
|
||||
<template>
|
||||
<div id="app">
|
||||
<!--可自定义按钮的样式、show/hide临界点、返回的位置 -->
|
||||
<!--如需文字提示,可在外部添加element的<el-tooltip></el-tooltip>元素 -->
|
||||
<el-tooltip placement="top" content="回到顶部">
|
||||
<back-to-top :custom-style="myBackToTopStyle" :visibility-height="300" :back-position="0" transition-name="fade"/>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BackToTop from './BackToTop'
|
||||
export default {
|
||||
name: 'app',
|
||||
components: { BackToTop},
|
||||
data() {
|
||||
return {
|
||||
myBackToTopStyle: {
|
||||
right: '50px',
|
||||
bottom: '50px',
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
borderRadius: '4px',
|
||||
lineHeight: '45px', // 请保持与高度一致以垂直居中
|
||||
background: '#e7eaf1'// 按钮的背景颜色
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
```
|
||||
|
||||
# 选项
|
||||
|
||||
| 属性 | 描述 | 类型 | 是否必须 | 默认值 |
|
||||
| :--------------: | :-----------------------------------------------: | :----: | :------: | :----: |
|
||||
| visibilityHeight | 当页面卷曲到多少高度时显示按钮 | Number | 否 | 400 |
|
||||
| backPosition | 点击按钮后回到页面顶部的高度 | Number | 否 | 0 |
|
||||
| customStyle | 自定义按钮样式 | Object | 否 | |
|
||||
| transitionName | 回到顶部时的动画效果,具体参考elementUI的动画效果 | String | 否 | fade |
|
||||
|
||||
|
||||
|
||||
# 效果图
|
||||
|
||||

|
||||
|
||||
|
||||
# 组件代码
|
||||
|
||||
完整代码请戳☞[Vue-Components-Library/BackToTop](https://github.com/wangjiachen199366/Vue-Components-Library/tree/master/BackToTop)
|
||||
|
||||
(完)
|
116
BackToTop/index.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<transition :name="transitionName">
|
||||
<div v-show="visible" :style="customStyle" class="back-to-ceiling" @click="backToTop">
|
||||
<svg width="16" height="16" viewBox="0 0 17 17" xmlns="http://www.w3.org/2000/svg" class="Icon Icon--backToTopArrow" aria-hidden="true" style="height: 16px; width: 16px;">
|
||||
<title>回到顶部</title>
|
||||
<g>
|
||||
<path d="M12.036 15.59c0 .55-.453.995-.997.995H5.032c-.55 0-.997-.445-.997-.996V8.584H1.03c-1.1 0-1.36-.633-.578-1.416L7.33.29c.39-.39 1.026-.385 1.412 0l6.878 6.88c.782.78.523 1.415-.58 1.415h-3.004v7.004z" fill-rule="evenodd"/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'BackToTop',
|
||||
props: {
|
||||
visibilityHeight: {
|
||||
type: Number,
|
||||
default: 400
|
||||
},
|
||||
backPosition: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default: function() {
|
||||
return {
|
||||
right: '50px',
|
||||
bottom: '50px',
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
'border-radius': '4px',
|
||||
'line-height': '45px',
|
||||
background: '#e7eaf1'
|
||||
}
|
||||
}
|
||||
},
|
||||
transitionName: {
|
||||
type: String,
|
||||
default: 'fade'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
interval: null,
|
||||
isMoving: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener('scroll', this.handleScroll)
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('scroll', this.handleScroll)
|
||||
if (this.interval) {
|
||||
clearInterval(this.interval)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleScroll() {
|
||||
this.visible = window.pageYOffset > this.visibilityHeight
|
||||
},
|
||||
backToTop() {
|
||||
if (this.isMoving) return
|
||||
const start = window.pageYOffset
|
||||
let i = 0
|
||||
this.isMoving = true
|
||||
this.interval = setInterval(() => {
|
||||
const next = Math.floor(this.easeInOutQuad(10 * i, start, -start, 500))
|
||||
if (next <= this.backPosition) {
|
||||
window.scrollTo(0, this.backPosition)
|
||||
clearInterval(this.interval)
|
||||
this.isMoving = false
|
||||
} else {
|
||||
window.scrollTo(0, next)
|
||||
}
|
||||
i++
|
||||
}, 16.7)
|
||||
},
|
||||
easeInOutQuad(t, b, c, d) {
|
||||
if ((t /= d / 2) < 1) return c / 2 * t * t + b
|
||||
return -c / 2 * (--t * (t - 2) - 1) + b
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.back-to-ceiling {
|
||||
position: fixed;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.back-to-ceiling:hover {
|
||||
background: #d5dbe7;
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity .5s;
|
||||
}
|
||||
|
||||
.fade-enter,
|
||||
.fade-leave-to {
|
||||
opacity: 0
|
||||
}
|
||||
|
||||
.back-to-ceiling .Icon {
|
||||
fill: #9aaabf;
|
||||
background: none;
|
||||
}
|
||||
</style>
|
BIN
Empty/1.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
Empty/2.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
Empty/3.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
Empty/4.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
91
Empty/README.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# 1. 前言
|
||||
|
||||
在日常开发中,页面上肯定有展示数据的需求,但是当某些时候该展示数据的地方此时数据为空时,就会留下一片空白,对用户体验不是很好,那么接下来我们就封装一个空数据时的占位展示图,告诉用户此时用户为空,并非数据没有加载出来,不用让用户盲目的等待。
|
||||
|
||||
# 2. 使用示例
|
||||
|
||||
该组件可以直接引入到项目中使用,示例如下:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div id="app">
|
||||
<div v-if="content.length"></div>
|
||||
<Empty v-else></Empty>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Empty from './Empty'
|
||||
export default {
|
||||
name: 'app',
|
||||
components: { Empty},
|
||||
data() {
|
||||
return {
|
||||
content:[]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
在上面代码中,假设你需要展示的内容是`content`,那么你就可以判断当内容有值时展示内容,当内容为空时展示空数据时的占位展示图。效果如下:
|
||||

|
||||
# 3. 组件可选属性
|
||||
|
||||
该组件除了可以直接使用外,还提供过了一些可选属性供个性化配置,提供可选属性如下:
|
||||
|
||||
| 属性名称 | 描述 | 类型 | 是否必须 | 默认值 |
|
||||
| :---------: | :------------------: | :----: | :------: | :------: |
|
||||
| description | 自定义描述内容 | String | 否 | 暂无数据 |
|
||||
| image | 自定义显示图片的路径 | String | 否 | 默认图片 |
|
||||
| imageStyle | 自定义显示图片的样式 | Object | 否 | - |
|
||||
|
||||
组件提供了3个可选属性,你可以这样去使用它们:
|
||||
|
||||
- 自定义描述内容
|
||||
|
||||
```html
|
||||
<Empty description="我是自定义内容"></Empty>
|
||||
```
|
||||

|
||||
- 显示自定义图片
|
||||
|
||||
```html
|
||||
<Empty description="显示网络图片" image="https://www.baidu.com/img/bd_logo1.png"></Empty>
|
||||
```
|
||||

|
||||
|
||||
|
||||
|
||||
```javascript
|
||||
<Empty description="显示项目资源图片" :image="require('@/assets/images/warn.png')"></Empty>
|
||||
```
|
||||

|
||||
|
||||
- 自定义显示图片样式
|
||||
|
||||
```html
|
||||
<template>
|
||||
<Empty
|
||||
description="显示网络图片"
|
||||
image="https://www.baidu.com/img/bd_logo1.png"
|
||||
:imageStyle="imageStyle">
|
||||
</Empty>
|
||||
</template>
|
||||
<script>
|
||||
data() {
|
||||
return {
|
||||
imageStyle:{
|
||||
width:'10px'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
# 4. 组件代码
|
||||
|
||||
完整代码请戳☞[Vue-Components-Library/Empty](https://github.com/NLRX/Vue-Components-Library/tree/master/Empty)
|
||||
|
||||
(完)
|
||||
|
90
Empty/index.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<div class="empty">
|
||||
<div class="image">
|
||||
<img :alt="description" :src="image" v-if="image" :style="imageStyle"/>
|
||||
<svg v-else width="184" height="152" viewBox="0 0 184 152" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<g transform="translate(24 31.67)">
|
||||
<ellipse fillOpacity=".8" fill="#F5F5F7" cx="67.797" cy="106.89" rx="67.797" ry="12.668" />
|
||||
<path
|
||||
d="M122.034 69.674L98.109 40.229c-1.148-1.386-2.826-2.225-4.593-2.225h-51.44c-1.766 0-3.444.839-4.592 2.225L13.56 69.674v15.383h108.475V69.674z"
|
||||
fill="#AEB8C2"
|
||||
/>
|
||||
<path
|
||||
d="M101.537 86.214L80.63 61.102c-1.001-1.207-2.507-1.867-4.048-1.867H31.724c-1.54 0-3.047.66-4.048 1.867L6.769 86.214v13.792h94.768V86.214z"
|
||||
fill="url(#linearGradient-1)"
|
||||
transform="translate(13.56)"
|
||||
/>
|
||||
<path
|
||||
d="M33.83 0h67.933a4 4 0 0 1 4 4v93.344a4 4 0 0 1-4 4H33.83a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4z"
|
||||
fill="#F5F5F7"
|
||||
/>
|
||||
<path
|
||||
d="M42.678 9.953h50.237a2 2 0 0 1 2 2V36.91a2 2 0 0 1-2 2H42.678a2 2 0 0 1-2-2V11.953a2 2 0 0 1 2-2zM42.94 49.767h49.713a2.262 2.262 0 1 1 0 4.524H42.94a2.262 2.262 0 0 1 0-4.524zM42.94 61.53h49.713a2.262 2.262 0 1 1 0 4.525H42.94a2.262 2.262 0 0 1 0-4.525zM121.813 105.032c-.775 3.071-3.497 5.36-6.735 5.36H20.515c-3.238 0-5.96-2.29-6.734-5.36a7.309 7.309 0 0 1-.222-1.79V69.675h26.318c2.907 0 5.25 2.448 5.25 5.42v.04c0 2.971 2.37 5.37 5.277 5.37h34.785c2.907 0 5.277-2.421 5.277-5.393V75.1c0-2.972 2.343-5.426 5.25-5.426h26.318v33.569c0 .617-.077 1.216-.221 1.789z"
|
||||
fill="#DCE0E6"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
d="M149.121 33.292l-6.83 2.65a1 1 0 0 1-1.317-1.23l1.937-6.207c-2.589-2.944-4.109-6.534-4.109-10.408C138.802 8.102 148.92 0 161.402 0 173.881 0 184 8.102 184 18.097c0 9.995-10.118 18.097-22.599 18.097-4.528 0-8.744-1.066-12.28-2.902z"
|
||||
fill="#DCE0E6"
|
||||
/>
|
||||
<g transform="translate(149.65 15.383)" fill="#FFF">
|
||||
<ellipse cx="20.654" cy="3.167" rx="2.849" ry="2.815" />
|
||||
<path d="M5.698 5.63H0L2.898.704zM9.259.704h4.985V5.63H9.259z" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<p class="description">{{description}}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Empty',
|
||||
props:{
|
||||
description:{
|
||||
required: false,
|
||||
type: String,
|
||||
default:'暂无数据'
|
||||
},
|
||||
image:{
|
||||
required: false,
|
||||
type: String,
|
||||
},
|
||||
imageStyle: {
|
||||
required: false,
|
||||
type: Object
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.empty {
|
||||
margin: 0 8px;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
text-align: center;
|
||||
}
|
||||
.image {
|
||||
height: 100px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.image img{
|
||||
height: 100%;
|
||||
}
|
||||
.image svg{
|
||||
height: 100%;
|
||||
margin: auto;
|
||||
}
|
||||
.description {
|
||||
margin: 0;
|
||||
color:rgba(0,0,0,0.5)
|
||||
}
|
||||
</style>
|
206
ExportExcel/Export2Excel.js
Normal file
@@ -0,0 +1,206 @@
|
||||
/* eslint-disable */
|
||||
require('file-saver');
|
||||
import XLSX from 'xlsx'
|
||||
|
||||
function generateArray(table) {
|
||||
var out = [];
|
||||
var rows = table.querySelectorAll('tr');
|
||||
var ranges = [];
|
||||
for (var R = 0; R < rows.length; ++R) {
|
||||
var outRow = [];
|
||||
var row = rows[R];
|
||||
var columns = row.querySelectorAll('td');
|
||||
for (var C = 0; C < columns.length; ++C) {
|
||||
var cell = columns[C];
|
||||
var colspan = cell.getAttribute('colspan');
|
||||
var rowspan = cell.getAttribute('rowspan');
|
||||
var cellValue = cell.innerText;
|
||||
if (cellValue !== "" && cellValue == +cellValue) cellValue = +cellValue;
|
||||
|
||||
//Skip ranges
|
||||
ranges.forEach(function (range) {
|
||||
if (R >= range.s.r && R <= range.e.r && outRow.length >= range.s.c && outRow.length <= range.e.c) {
|
||||
for (var i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null);
|
||||
}
|
||||
});
|
||||
|
||||
//Handle Row Span
|
||||
if (rowspan || colspan) {
|
||||
rowspan = rowspan || 1;
|
||||
colspan = colspan || 1;
|
||||
ranges.push({
|
||||
s: {
|
||||
r: R,
|
||||
c: outRow.length
|
||||
},
|
||||
e: {
|
||||
r: R + rowspan - 1,
|
||||
c: outRow.length + colspan - 1
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
//Handle Value
|
||||
outRow.push(cellValue !== "" ? cellValue : null);
|
||||
|
||||
//Handle Colspan
|
||||
if (colspan)
|
||||
for (var k = 0; k < colspan - 1; ++k) outRow.push(null);
|
||||
}
|
||||
out.push(outRow);
|
||||
}
|
||||
return [out, ranges];
|
||||
};
|
||||
|
||||
function datenum(v, date1904) {
|
||||
if (date1904) v += 1462;
|
||||
var epoch = Date.parse(v);
|
||||
return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000);
|
||||
}
|
||||
|
||||
function sheet_from_array_of_arrays(data, opts) {
|
||||
var ws = {};
|
||||
var range = {
|
||||
s: {
|
||||
c: 10000000,
|
||||
r: 10000000
|
||||
},
|
||||
e: {
|
||||
c: 0,
|
||||
r: 0
|
||||
}
|
||||
};
|
||||
for (var R = 0; R != data.length; ++R) {
|
||||
for (var C = 0; C != data[R].length; ++C) {
|
||||
if (range.s.r > R) range.s.r = R;
|
||||
if (range.s.c > C) range.s.c = C;
|
||||
if (range.e.r < R) range.e.r = R;
|
||||
if (range.e.c < C) range.e.c = C;
|
||||
var cell = {
|
||||
v: data[R][C]
|
||||
};
|
||||
if (cell.v == null) continue;
|
||||
var cell_ref = XLSX.utils.encode_cell({
|
||||
c: C,
|
||||
r: R
|
||||
});
|
||||
|
||||
if (typeof cell.v === 'number') cell.t = 'n';
|
||||
else if (typeof cell.v === 'boolean') cell.t = 'b';
|
||||
else if (cell.v instanceof Date) {
|
||||
cell.t = 'n';
|
||||
cell.z = XLSX.SSF._table[14];
|
||||
cell.v = datenum(cell.v);
|
||||
} else cell.t = 's';
|
||||
|
||||
ws[cell_ref] = cell;
|
||||
}
|
||||
}
|
||||
if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range);
|
||||
return ws;
|
||||
}
|
||||
|
||||
function Workbook() {
|
||||
if (!(this instanceof Workbook)) return new Workbook();
|
||||
this.SheetNames = [];
|
||||
this.Sheets = {};
|
||||
}
|
||||
|
||||
function s2ab(s) {
|
||||
var buf = new ArrayBuffer(s.length);
|
||||
var view = new Uint8Array(buf);
|
||||
for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
|
||||
return buf;
|
||||
}
|
||||
|
||||
export function export_table_to_excel(id) {
|
||||
var theTable = document.getElementById(id);
|
||||
var oo = generateArray(theTable);
|
||||
var ranges = oo[1];
|
||||
|
||||
/* original data */
|
||||
var data = oo[0];
|
||||
var ws_name = "SheetJS";
|
||||
|
||||
var wb = new Workbook(),
|
||||
ws = sheet_from_array_of_arrays(data);
|
||||
|
||||
/* add ranges to worksheet */
|
||||
// ws['!cols'] = ['apple', 'banan'];
|
||||
ws['!merges'] = ranges;
|
||||
|
||||
/* add worksheet to workbook */
|
||||
wb.SheetNames.push(ws_name);
|
||||
wb.Sheets[ws_name] = ws;
|
||||
|
||||
var wbout = XLSX.write(wb, {
|
||||
bookType: 'xlsx',
|
||||
bookSST: false,
|
||||
type: 'binary'
|
||||
});
|
||||
|
||||
saveAs(new Blob([s2ab(wbout)], {
|
||||
type: "application/octet-stream"
|
||||
}), "test.xlsx")
|
||||
}
|
||||
|
||||
export function export_json_to_excel({
|
||||
header,
|
||||
data,
|
||||
filename,
|
||||
autoWidth = true,
|
||||
bookType= 'xlsx'
|
||||
} = {}) {
|
||||
/* original data */
|
||||
filename = filename || 'excel-list'
|
||||
data = [...data]
|
||||
data.unshift(header);
|
||||
var ws_name = "SheetJS";
|
||||
var wb = new Workbook(),
|
||||
ws = sheet_from_array_of_arrays(data);
|
||||
|
||||
if (autoWidth) {
|
||||
/*设置worksheet每列的最大宽度*/
|
||||
const colWidth = data.map(row => row.map(val => {
|
||||
/*先判断是否为null/undefined*/
|
||||
if (val == null) {
|
||||
return {
|
||||
'wch': 10
|
||||
};
|
||||
}
|
||||
/*再判断是否为中文*/
|
||||
else if (val.toString().charCodeAt(0) > 255) {
|
||||
return {
|
||||
'wch': val.toString().length * 2
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'wch': val.toString().length
|
||||
};
|
||||
}
|
||||
}))
|
||||
/*以第一行为初始值*/
|
||||
let result = colWidth[0];
|
||||
for (let i = 1; i < colWidth.length; i++) {
|
||||
for (let j = 0; j < colWidth[i].length; j++) {
|
||||
if (result[j]['wch'] < colWidth[i][j]['wch']) {
|
||||
result[j]['wch'] = colWidth[i][j]['wch'];
|
||||
}
|
||||
}
|
||||
}
|
||||
ws['!cols'] = result;
|
||||
}
|
||||
|
||||
/* add worksheet to workbook */
|
||||
wb.SheetNames.push(ws_name);
|
||||
wb.Sheets[ws_name] = ws;
|
||||
|
||||
var wbout = XLSX.write(wb, {
|
||||
bookType: bookType,
|
||||
bookSST: false,
|
||||
type: 'binary'
|
||||
});
|
||||
saveAs(new Blob([s2ab(wbout)], {
|
||||
type: "application/octet-stream"
|
||||
}), `${filename}.${bookType}`);
|
||||
}
|
120
ExportExcel/README.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# 介绍
|
||||
|
||||
这是一个可以将页面中的表格数据导出为`Excel`文件的功能组件,该组件一般与表格一起使用,将表格数据传给组件,然后通过点击组件按钮可将表格中的数据导出成`Excel`文件。
|
||||
|
||||
# 使用方法
|
||||
|
||||
由于封装该组件内部引用了`xlsx.js`,`file-saver.js`和`elementUI`,因此在使用该组件时,请先安装如下依赖:
|
||||
|
||||
```shell
|
||||
npm install xlsx file-saver element-ui --save
|
||||
```
|
||||
|
||||
安装好依赖后,只需将该组件文件夹`ExportExcel`导入到现有项目中即可使用。
|
||||
|
||||
|
||||
# 使用示例
|
||||
|
||||
```html
|
||||
<template>
|
||||
<div id="app">
|
||||
<export-excel v-if="list !== null" :list="list" :tHeader="tHeader" :tValue="tValue"></export-excel>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ExportExcel from './ExportExcel'
|
||||
export default {
|
||||
name: 'app',
|
||||
components: { ExportExcel},
|
||||
data() {
|
||||
return {
|
||||
list:null,
|
||||
tHeader:['Id', '告警类型', '告警内容', '告警时间(段)', '告警次数'],
|
||||
tValue:['id', 'type', 'content', 'time', 'count'],
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
```
|
||||
|
||||
# 选项
|
||||
|
||||
| 属性 | 描述 | 类型 | 是否必须 |
|
||||
| :------: | :------------------: | :---: | :------: |
|
||||
| list | 由后端返回的表格数据 | Array | 是 |
|
||||
| tHeader | 导出的Excel文件表头标题 | Array | 是 |
|
||||
| tValue | 要将表格数据中的哪些字段作为数据导出至Excel,与表头一一对应 | Array | 是 |
|
||||
| filename | 导出的Excel文件名,默认为“导出数据.xlsx” | String | 否 |
|
||||
|
||||
# 选项说明
|
||||
|
||||
**关于选项中的`tHeader`和`tValue`说明如下:**
|
||||
|
||||
例如将如下表格数据导出成Exlcel:
|
||||
|
||||

|
||||
|
||||
其中表头数据为:
|
||||
|
||||

|
||||
|
||||
所以`tHeader`为:
|
||||
|
||||
```javascript
|
||||
tHeader:['Id', '告警类型', '告警内容', '告警时间(段)', '告警次数']
|
||||
```
|
||||
|
||||
后端返回的表格数据`list`为:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id":1,
|
||||
"type":"",
|
||||
"content":"",
|
||||
"time":"",
|
||||
"count":"",
|
||||
},
|
||||
{
|
||||
"id":2,
|
||||
"type":"",
|
||||
"content":"",
|
||||
"time":"",
|
||||
"count":"",
|
||||
},
|
||||
//....
|
||||
]
|
||||
```
|
||||
|
||||
其中:
|
||||
|
||||
- list中的id------->表头的ID
|
||||
|
||||
- list中的type------->表头的'告警类型'
|
||||
|
||||
- list中的content------->表头的'告警内容'
|
||||
|
||||
- list中的time------->表头的'告警时间(段)'
|
||||
|
||||
- list中的count------->表头的'告警次数'
|
||||
|
||||
所有`tValue`为:
|
||||
|
||||
```javascript
|
||||
tValue:['id', 'type', 'content', 'time', 'count']
|
||||
```
|
||||
|
||||
|
||||
|
||||
# 效果图
|
||||
|
||||

|
||||
|
||||
|
||||
# 组件代码
|
||||
|
||||
完整代码请戳☞[Vue-Components-Library/ExportExcel](https://github.com/wangjiachen199366/Vue-Components-Library/tree/master/ExportExcel)
|
||||
|
||||
(完)
|
58
ExportExcel/index.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<el-button :loading="downloadLoading" type="primary" @click="handleDownload">导出 Excel</el-button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'ExportExcel',
|
||||
props: {
|
||||
list: {
|
||||
required: true,
|
||||
type: Array
|
||||
},
|
||||
tHeader: {
|
||||
required: true,
|
||||
type: Array
|
||||
},
|
||||
tValue: {
|
||||
required: true,
|
||||
type: Array
|
||||
},
|
||||
filename: {
|
||||
type: String,
|
||||
default: '导出数据'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
downloadLoading: false,
|
||||
autoWidth: true,
|
||||
bookType: 'xlsx'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleDownload() {
|
||||
this.downloadLoading = true
|
||||
import('./Export2Excel').then(excel => {
|
||||
const data = this.formatJson(this.tValue, this.list)
|
||||
excel.export_json_to_excel({
|
||||
header: this.tHeader,
|
||||
data,
|
||||
filename: this.filename,
|
||||
autoWidth: this.autoWidth,
|
||||
bookType: this.bookType
|
||||
})
|
||||
this.downloadLoading = false
|
||||
})
|
||||
},
|
||||
formatJson(filterVal, jsonData) {
|
||||
return jsonData.map(v => filterVal.map(j => {
|
||||
return v[j]
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
14
JTopoInVue/App.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<jtopo/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import jtopo from './components/jTopo'
|
||||
export default {
|
||||
name: 'app',
|
||||
components:{jtopo}
|
||||
}
|
||||
</script>
|
||||
|
44
JTopoInVue/README.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# 1.前言
|
||||
|
||||
[jTopo(Javascript Topology library)]([http://www.jtopo.com](http://www.jtopo.com/)) 是一款完全基于HTML5 Canvas的关系、拓扑图形化界面开发工具包。其体积小,性能优异,由一群开发爱好者来维护。唯一感觉不足的是它是一个纯`js`库,没有像使用`ES6`语法,不能像模块化开发那样使用`import`导入,
|
||||
|
||||
由于博主的项目是使用vue-cli搭建的模块化开发项目,想要使用第三方库最好的方式是通过`npm install xxx`安装,然后在项目里`import xxx`来使用。但是在`JTopo`官网上并没有发现有该库的`npm`包,在`www.npmjs.com`上搜索`JTopo`,虽然找到了该库的`npm`包,但是这些包都是由一些个人开发者通过修改源码上传的,并且年限过久,博主担心直接使用的话可能会有一些诡异的`bug`,所以博主研究了一下,如何在`vue-cli`项目中直接导入第三方`js`库,幸运的是,很快就找到了办法,并且很容易哈,现将方法记录下来,并提供demo,供大家参考。
|
||||
|
||||
# 2.解决办法
|
||||
|
||||
我们知道,无论是什么项目,最终通过打包后跑在浏览器上的肯定是一个`html`文件,在`Vue`中就是根目录下的`index.html`,在该文件中会将`webpack`打包后的`build.js`文件通过`<script>`标签方式引入,既然如此,我们可以大胆想象一下,我们可以认为`jtopo.js`就是`webpack`打包输出的文件,我们也将其手动在`index.html`文件中通过`<script>`标签方式引入是不是就可以使用了呢。通过实验,果真如此。
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JTopoInVue</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="/lib/jtopo-0.4.8-min.js"></script>
|
||||
<script src="/dist/build.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
这样引入之后,我们就可以在项目中按照`jtopo`官方文档那样使用了该库啦。
|
||||
|
||||
# 3.不足之处
|
||||
|
||||
`jtopo`官网还提供了工具栏,该工具栏功能是在`toolbar.js`中实现的,而该`js`文件内部依赖了`jQuery`,所以要想在项目中使用该工具栏,必须安装`jQuery`,而博主在项目中没有使用工具栏,所以就没有在继续研究,如果有这方面需求的小伙伴可自行研究使用。
|
||||
|
||||
# 4.运行demo
|
||||
|
||||
``` bash
|
||||
# install dependencies
|
||||
npm install
|
||||
|
||||
# serve with hot reload at localhost:8080
|
||||
npm run dev
|
||||
|
||||
# build for production with minification
|
||||
npm run build
|
||||
```
|
||||
|
35
JTopoInVue/components/jTopo.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<div>
|
||||
<canvas width="800" height="580" id="canvas"></canvas>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: "jtopo",
|
||||
mounted() {
|
||||
this.initTopo()
|
||||
},
|
||||
methods: {
|
||||
initTopo() {
|
||||
let canvas = document.getElementById("canvas");
|
||||
|
||||
let stage = new JTopo.Stage(canvas);
|
||||
|
||||
// stage.eagleEye.visible = true;
|
||||
stage.wheelZoom = 1.2; //缩放比例
|
||||
let scene = new JTopo.Scene(stage);
|
||||
scene.background = '/static/bg.jpg';
|
||||
|
||||
var node = new JTopo.Node("难凉热血");
|
||||
node.setLocation(409, 269);
|
||||
scene.add(node);
|
||||
|
||||
|
||||
// 将画布居中
|
||||
stage.centerAndZoom();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
12
JTopoInVue/index.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JTopoInVue</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="/lib/jtopo-0.4.8-min.js"></script>
|
||||
<script src="/dist/build.js"></script>
|
||||
</body>
|
||||
</html>
|
3
JTopoInVue/lib/jtopo-0.4.8-min.js
vendored
Normal file
7
JTopoInVue/main.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
|
||||
new Vue({
|
||||
el: '#app',
|
||||
render: h => h(App),
|
||||
})
|
34
JTopoInVue/package.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "JTopo in Vue",
|
||||
"description": "JTopo in Vue",
|
||||
"version": "1.0.0",
|
||||
"author": "NLRX",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
|
||||
"build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^2.5.11"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not ie <= 8"
|
||||
],
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-loader": "^7.1.2",
|
||||
"babel-plugin-component": "^1.1.1",
|
||||
"babel-preset-env": "^1.6.0",
|
||||
"babel-preset-stage-3": "^6.24.1",
|
||||
"cross-env": "^5.0.5",
|
||||
"css-loader": "^0.28.7",
|
||||
"file-loader": "^1.1.4",
|
||||
"vue-loader": "^13.0.5",
|
||||
"vue-template-compiler": "^2.4.4",
|
||||
"webpack": "^3.6.0",
|
||||
"webpack-dev-server": "^2.9.1"
|
||||
}
|
||||
}
|
BIN
JTopoInVue/static/bg.jpg
Normal file
After Width: | Height: | Size: 56 KiB |
82
JTopoInVue/webpack.config.js
Normal file
@@ -0,0 +1,82 @@
|
||||
var path = require('path')
|
||||
var webpack = require('webpack')
|
||||
|
||||
module.exports = {
|
||||
entry: './main.js',
|
||||
output: {
|
||||
path: path.resolve(__dirname, './dist'),
|
||||
publicPath: '/dist/',
|
||||
filename: 'build.js'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
'vue-style-loader',
|
||||
'css-loader'
|
||||
],
|
||||
}, {
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader',
|
||||
options: {
|
||||
loaders: {
|
||||
}
|
||||
// other vue-loader options go here
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules/
|
||||
},
|
||||
{
|
||||
test: /\.(eot|svg|ttf|woff|woff2)(\?\S*)?$/,
|
||||
loader: 'file-loader'
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpg|gif|svg)$/,
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: '[name].[ext]?[hash]'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'vue$': 'vue/dist/vue.esm.js'
|
||||
},
|
||||
extensions: ['*', '.js', '.vue', '.json']
|
||||
},
|
||||
devServer: {
|
||||
historyApiFallback: true,
|
||||
noInfo: true,
|
||||
overlay: true
|
||||
},
|
||||
performance: {
|
||||
hints: false
|
||||
},
|
||||
devtool: '#eval-source-map'
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports.devtool = '#source-map'
|
||||
// http://vue-loader.vuejs.org/en/workflow/production.html
|
||||
module.exports.plugins = (module.exports.plugins || []).concat([
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: '"production"'
|
||||
}
|
||||
}),
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
sourceMap: true,
|
||||
compress: {
|
||||
warnings: false
|
||||
}
|
||||
}),
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
minimize: true
|
||||
})
|
||||
])
|
||||
}
|
87
MergeTableCell/App.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<el-table
|
||||
:data="data"
|
||||
border
|
||||
fit
|
||||
stripe
|
||||
:span-method="cellMerge"
|
||||
highlight-current-row
|
||||
size="small"
|
||||
style="width: 100%;">
|
||||
<el-table-column prop="ruleId_1" label="指标编码" align="center" width="100"></el-table-column>
|
||||
<el-table-column prop="checkRange" label="检查范围" align="center" width="100"></el-table-column>
|
||||
<el-table-column prop="ruleId_2" label="指标编码" align="center" width="100"></el-table-column>
|
||||
<el-table-column prop="checkContent" label="检查内容" align="center" width="100"></el-table-column>
|
||||
<el-table-column prop="ruleId_3" label="指标编码" align="center" width="120"></el-table-column>
|
||||
<el-table-column prop="checkItem" label="检查项" align="center" width="300"></el-table-column>
|
||||
<el-table-column prop="ruleId_4" label="指标编码" align="center" width="200"></el-table-column>
|
||||
<el-table-column prop="checkPoint" label="检查要点" align="left" header-align="center"></el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue'
|
||||
import Element from 'element-ui'
|
||||
import 'element-ui/lib/theme-chalk/index.css'
|
||||
|
||||
import data from './data'
|
||||
|
||||
Vue.use(Element)
|
||||
export default {
|
||||
name: 'app',
|
||||
data () {
|
||||
return {
|
||||
data:data,
|
||||
spanArr: [],//二维数组,用于存放单元格合并规则
|
||||
position: 0,//用于存储相同项的开始index
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.rowspan(0,'ruleId_1');
|
||||
this.rowspan(1,'checkRange');
|
||||
this.rowspan(2,'ruleId_2');
|
||||
this.rowspan(3,'checkContent');
|
||||
this.rowspan(4,'ruleId_3');
|
||||
this.rowspan(5,'checkItem');
|
||||
this.rowspan(6,'ruleId_4');
|
||||
this.rowspan(7,'checkPoint');
|
||||
},
|
||||
methods:{
|
||||
rowspan(idx, prop) {
|
||||
this.spanArr[idx] = [];
|
||||
this.position = 0;
|
||||
this.data.forEach((item,index) => {
|
||||
if( index === 0){
|
||||
this.spanArr[idx].push(1);
|
||||
this.position = 0;
|
||||
}else{
|
||||
if(this.data[index][prop] === this.data[index-1][prop] ){
|
||||
this.spanArr[idx][this.position] += 1;//有相同项
|
||||
this.spanArr[idx].push(0); // 名称相同后往数组里面加一项0
|
||||
}else{
|
||||
this.spanArr[idx].push(1);//同列的前后两行单元格不相同
|
||||
this.position = index;
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
//表格单元格合并
|
||||
cellMerge({ row, column, rowIndex, columnIndex }) {
|
||||
for(let i = 0; i<this.spanArr.length; i++) {
|
||||
if(columnIndex === i){
|
||||
const _row = this.spanArr[i][rowIndex];
|
||||
const _col = _row>0 ? 1 : 0;
|
||||
// console.log('第'+rowIndex+'行','第'+i+'列','rowspan:'+_row,'colspan:'+_col)
|
||||
return {
|
||||
rowspan: _row,
|
||||
colspan: _col
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
13
MergeTableCell/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# 1.运行demo
|
||||
|
||||
``` bash
|
||||
# install dependencies
|
||||
npm install
|
||||
|
||||
# serve with hot reload at localhost:8080
|
||||
npm run dev
|
||||
|
||||
# build for production with minification
|
||||
npm run build
|
||||
```
|
||||
|
22
MergeTableCell/data.js
Normal file
@@ -0,0 +1,22 @@
|
||||
let data = [
|
||||
{ ruleId_1:'u301',checkRange:'网络安全工作基本情况',ruleId_2:'u30100501',checkContent:'网络安全工作的组织部署',ruleId_3:'u30100501101',checkItem:'检查网络安全协调领导机构运行情况', ruleId_4:'u30100501101101',checkPoint:'等级保护联络员是否参加公安网安部门组织召开的等级保护联络员会议?' },
|
||||
{ ruleId_1:'u301',checkRange:'网络安全工作基本情况',ruleId_2:'u30100501',checkContent:'网络安全工作的组织部署',ruleId_3:'u30100501101',checkItem:'检查网络安全协调领导机构运行情况', ruleId_4:'u30100501101102',checkPoint:'联络员是否以书面形式向主管领导汇报公安网安部门在联络员会议上通报的内容?' },
|
||||
{ ruleId_1:'u301',checkRange:'网络安全工作基本情况',ruleId_2:'u30100501',checkContent:'网络安全工作的组织部署',ruleId_3:'u30100502102',checkItem:'检查网络安全责任部门运行情况', ruleId_4:'u30100501102101',checkPoint:'网络安全责任部门是否对本单位的网络安全工作下发文件或召开会议等方式进行业务指导?' },
|
||||
{ ruleId_1:'u301',checkRange:'网络安全工作基本情况',ruleId_2:'u30100501',checkContent:'网络安全工作的组织部署',ruleId_3:'u30100502102',checkItem:'检查网络安全责任部门运行情况', ruleId_4:'u30100501102102',checkPoint:'网络安全责任部门是否对本单位的网络安全工作听取汇报或现场检查等方式进行监督检查?' },
|
||||
{ ruleId_1:'u301',checkRange:'网络安全工作基本情况',ruleId_2:'u30100501',checkContent:'网络安全工作的组织部署',ruleId_3:'u30100502103',checkItem:'检查网络安全工作文件执行情况', ruleId_4:'u30100501103101',checkPoint:'网络安全责任部门是否对本单位的网络安全工作文件或方案明确网络安全工作所需要执行的内容?' },
|
||||
{ ruleId_1:'u301',checkRange:'网络安全工作基本情况',ruleId_2:'u30100501',checkContent:'网络安全工作的组织部署',ruleId_3:'u30100502103',checkItem:'检查网络安全工作文件执行情况', ruleId_4:'u30100501103102',checkPoint:'是否有网络安全工作文件或方案的执行情况的总结或汇报?' },
|
||||
{ ruleId_1:'u301',checkRange:'网络安全工作基本情况',ruleId_2:'u30100501',checkContent:'网络安全工作的组织部署',ruleId_3:'u30100502104',checkItem:'检查网络安全工作会议、业务培训情况', ruleId_4:'u30100501104101',checkPoint:'是否有组织人员培训的工作会议记录或文件?' },
|
||||
{ ruleId_1:'u301',checkRange:'网络安全工作基本情况',ruleId_2:'u30100501',checkContent:'网络安全工作的组织部署',ruleId_3:'u30100502104',checkItem:'检查网络安全工作会议、业务培训情况', ruleId_4:'u30100501104102',checkPoint:'受训人员的业务水平是否明显提高?' },
|
||||
{ ruleId_1:'u301',checkRange:'网络安全工作基本情况',ruleId_2:'u30100501',checkContent:'网络安全工作的组织部署',ruleId_3:'u30100502105',checkItem:'检查主要领导对网络安全工作的重视情况', ruleId_4:'u30100501105101',checkPoint:'是否将网络安全工作的执行情况纳入到年度考核指标?' },
|
||||
{ ruleId_1:'u301', checkRange: '网络安全工作基本情况', ruleId_2: 'u30100501', checkContent: '网络安全工作的组织部署', ruleId_3: 'u30100502105', checkItem: '检查主要领导对网络安全工作的重视情况', ruleId_4: 'u30100501105102', checkPoint: '开展网络安全工作的经费是否纳入年度预算?' },
|
||||
|
||||
{ ruleId_1:'u301',checkRange:'网络安全工作基本情况',ruleId_2:'u30100502',checkContent:'网络安全责任制落实情况',ruleId_3:'u30100502101',checkItem:'检查网络安全领导机构执行情况', ruleId_4:'u30100502101101',checkPoint:'是否有网络安全工作的领导责任制?' },
|
||||
{ ruleId_1:'u301',checkRange:'网络安全工作基本情况',ruleId_2:'u30100502',checkContent:'网络安全责任制落实情况',ruleId_3:'u30100502101',checkItem:'检查网络安全领导机构执行情况', ruleId_4:'u30100502101102',checkPoint:'对于网络安全领导是否有网络安全工作的考核指标?' },
|
||||
{ ruleId_1:'u301',checkRange:'网络安全工作基本情况',ruleId_2:'u30100502',checkContent:'网络安全责任制落实情况',ruleId_3:'u30100502102',checkItem:'检查网络安全职能部门的执行情况', ruleId_4:'u30100502102101',checkPoint:'是否对本单位的网络安全工作发布文件或召开会议等方式进行业务指导?' },
|
||||
{ ruleId_1:'u301',checkRange:'网络安全工作基本情况',ruleId_2:'u30100502',checkContent:'网络安全责任制落实情况',ruleId_3:'u30100502102',checkItem:'检查网络安全职能部门的执行情况', ruleId_4:'u30100502102102',checkPoint:'是否对本单位的网络安全工作听取汇报或现场检查等方式进行监督?' },
|
||||
{ ruleId_1:'u301',checkRange:'网络安全工作基本情况',ruleId_2:'u30100502',checkContent:'网络安全责任制落实情况',ruleId_3:'u30100502103',checkItem:'检查网络安全责任追究制度执行情况', ruleId_4:'u30100502103101',checkPoint:'是否对所有的网络安全事故/事件有详细记录?' },
|
||||
{ ruleId_1:'u301',checkRange:'网络安全工作基本情况',ruleId_2:'u30100502',checkContent:'网络安全责任制落实情况',ruleId_3:'u30100502103',checkItem:'检查网络安全责任追究制度执行情况', ruleId_4:'u30100502103102',checkPoint:'是否按照网络安全责任追究制度进行处理?' },
|
||||
|
||||
]
|
||||
|
||||
export default data
|
11
MergeTableCell/index.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>mergeTableCell</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="/dist/build.js"></script>
|
||||
</body>
|
||||
</html>
|
7
MergeTableCell/main.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
|
||||
new Vue({
|
||||
el: '#app',
|
||||
render: h => h(App),
|
||||
})
|
35
MergeTableCell/package.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "mergeTableCell",
|
||||
"description": "mergeTableCell",
|
||||
"version": "1.0.0",
|
||||
"author": "NLRX",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
|
||||
"build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^2.5.11"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not ie <= 8"
|
||||
],
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-loader": "^7.1.2",
|
||||
"babel-plugin-component": "^1.1.1",
|
||||
"babel-preset-env": "^1.6.0",
|
||||
"babel-preset-stage-3": "^6.24.1",
|
||||
"element-ui": "^2.4.10",
|
||||
"cross-env": "^5.0.5",
|
||||
"css-loader": "^0.28.7",
|
||||
"file-loader": "^1.1.4",
|
||||
"vue-loader": "^13.0.5",
|
||||
"vue-template-compiler": "^2.4.4",
|
||||
"webpack": "^3.6.0",
|
||||
"webpack-dev-server": "^2.9.1"
|
||||
}
|
||||
}
|
82
MergeTableCell/webpack.config.js
Normal file
@@ -0,0 +1,82 @@
|
||||
var path = require('path')
|
||||
var webpack = require('webpack')
|
||||
|
||||
module.exports = {
|
||||
entry: './main.js',
|
||||
output: {
|
||||
path: path.resolve(__dirname, './dist'),
|
||||
publicPath: '/dist/',
|
||||
filename: 'build.js'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
'vue-style-loader',
|
||||
'css-loader'
|
||||
],
|
||||
}, {
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader',
|
||||
options: {
|
||||
loaders: {
|
||||
}
|
||||
// other vue-loader options go here
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules/
|
||||
},
|
||||
{
|
||||
test: /\.(eot|svg|ttf|woff|woff2)(\?\S*)?$/,
|
||||
loader: 'file-loader'
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpg|gif|svg)$/,
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: '[name].[ext]?[hash]'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'vue$': 'vue/dist/vue.esm.js'
|
||||
},
|
||||
extensions: ['*', '.js', '.vue', '.json']
|
||||
},
|
||||
devServer: {
|
||||
historyApiFallback: true,
|
||||
noInfo: true,
|
||||
overlay: true
|
||||
},
|
||||
performance: {
|
||||
hints: false
|
||||
},
|
||||
devtool: '#eval-source-map'
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports.devtool = '#source-map'
|
||||
// http://vue-loader.vuejs.org/en/workflow/production.html
|
||||
module.exports.plugins = (module.exports.plugins || []).concat([
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: '"production"'
|
||||
}
|
||||
}),
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
sourceMap: true,
|
||||
compress: {
|
||||
warnings: false
|
||||
}
|
||||
}),
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
minimize: true
|
||||
})
|
||||
])
|
||||
}
|
57
Pagination/README.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# 介绍
|
||||
|
||||
这是一个是基于`element-UI`的分页组件基础上,进行了二次封装的分页组件,在展示数据时,该分页组件采用了每显示一页数据,只请求当前页面的数据的请求策略,从而避免了一次性将数据全部请求所造成的资源浪费。
|
||||
|
||||
# 使用方法
|
||||
|
||||
由于该组件是基于`element-UI`的分页组件进行二次封装,所以在使用该组件时请务必安装`element-UI`([安装方式猛戳这里](http://element-cn.eleme.io/#/zh-CN/component/installation)),安装好`element-UI`后,只需将该组件文件夹`Pagination`导入到现有项目中即可使用。
|
||||
|
||||
# 示例
|
||||
|
||||
```html
|
||||
<template>
|
||||
<pagination v-show="total>0" :total="total" :page.sync="page" :limit.sync="limit" @pagination="getList" />
|
||||
</template>
|
||||
<script>
|
||||
import Pagination from '@/components/Pagination';
|
||||
export default {
|
||||
components: { Pagination },
|
||||
data () {
|
||||
return {
|
||||
list:null, //请求回来填充表格的数据
|
||||
total:0, //数据总条数
|
||||
page: 1, //默认显示第1页
|
||||
limit: 10 //默认一次显示10条数据
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
getlist(){
|
||||
var start = (this.page - 1) * this.limit;
|
||||
var end = this.page * this.limit;
|
||||
this.$axios.get(url + '?start=' + start + '&end=' + end)
|
||||
.then(response => {
|
||||
this.list = response.data.items;
|
||||
this.total = response.data.total;
|
||||
resolve();
|
||||
}).catch(err => {
|
||||
reject(err);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
# 选项
|
||||
|
||||
| 属性 | 描述 | 类型 | 默认值 | 是否必须 |
|
||||
| :----------: | :--: | :--: | :--: | :----------: |
|
||||
| total | 数据总数 | Number | 0 | 是 |
|
||||
| page | 当前页码 | Number | 1 | 是 |
|
||||
| limit | 每页显示数据条目个数 | Number | 10 | 是 |
|
||||
| pageSizes | 每页显示个数选择器的选项设置 | Array | [10,20,30] | 否 |
|
||||
| layout | 组件布局 | String | 'total, sizes, prev, pager, next, jumper' | 否 |
|
||||
| background | 是否为分页按钮添加背景色 | Boolean | true | 否 |
|
||||
|
||||
# 效果图
|
||||

|
81
Pagination/index.vue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<div class="pagination-container">
|
||||
<el-pagination
|
||||
:background="background"
|
||||
:current-page.sync="currentPage"
|
||||
:page-size.sync="pageSize"
|
||||
:page-sizes.sync="pageSizes"
|
||||
:layout="layout"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'Pagination',
|
||||
props: {
|
||||
total: {
|
||||
required: true,
|
||||
type: Number
|
||||
},
|
||||
page: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
pageSizes: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [10,20,30]
|
||||
}
|
||||
},
|
||||
layout: {
|
||||
type: String,
|
||||
default: 'total, sizes, prev, pager, next, jumper'
|
||||
},
|
||||
background: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentPage: {
|
||||
get() {
|
||||
return this.page;
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:page', val);
|
||||
}
|
||||
},
|
||||
pageSize: {
|
||||
get() {
|
||||
return this.limit;
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:limit', val);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSizeChange(val) {
|
||||
this.$emit('pagination', { page: this.currentPage, limit: val });
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.$emit('pagination', { page: val, limit: this.pageSize });
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pagination-container {
|
||||
background: #fff;
|
||||
padding: 32px 16px;
|
||||
}
|
||||
</style>
|
BIN
Pagination/效果图.gif
Normal file
After Width: | Height: | Size: 1.1 MiB |
22
README.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Vue-Components-Library
|
||||
打造一个自己的Vue组件库。
|
||||
|
||||
##### [Vue+element UI实现分页组件](https://www.cnblogs.com/wangjiachen666/p/9545456.html)——[Pagination](https://github.com/NLRX/Vue-Components-Library/tree/master/Pagination)
|
||||
|
||||
##### [vue+element UI以组件递归方式实现多级导航菜单](https://www.cnblogs.com/wangjiachen666/p/9444488.html)——[SideBar](https://github.com/NLRX/Vue-Components-Library/tree/master/SideBar)
|
||||
|
||||
##### [vue+element UI + axios封装文件上传及进度条组件](https://www.cnblogs.com/wangjiachen666/p/9700730.html)——[UploadFile](https://github.com/NLRX/Vue-Components-Library/tree/master/UploadFile)
|
||||
|
||||
##### [Vue+element UI实现表格数据导出Excel组件](https://www.cnblogs.com/wangjiachen666/p/10140118.html)——[ExportExcel](https://github.com/NLRX/Vue-Components-Library/tree/master/ExportExcel)
|
||||
|
||||
##### [Vue+element UI实现“回到顶部”按钮组件](https://www.cnblogs.com/wangjiachen666/p/10142184.html)——[BackToTop](https://github.com/NLRX/Vue-Components-Library/tree/master/BackToTop)
|
||||
|
||||
##### [如何在Vue项目中给路由跳转加上进度条](https://www.cnblogs.com/wangjiachen666/p/10163164.html)——[nprogresBar](https://github.com/NLRX/Vue-Components-Library/tree/master/nprogresBar)
|
||||
|
||||
##### [如何在Vue-cli项目中使用JTopo](https://www.cnblogs.com/wangjiachen666/p/11022686.html)——[JTopoInVue](https://github.com/NLRX/Vue-Components-Library/tree/master/JTopoInVue)
|
||||
|
||||
##### [如何在Vue中,当鼠标hover上元素时,给元素加遮罩层](https://www.cnblogs.com/wangjiachen666/p/11090908.html)——[VueHoverMask](https://github.com/NLRX/Vue-Components-Library/tree/master/VueHoverMask)
|
||||
|
||||
##### [如何让elemengUI中的表格组件相同内容的单元格自动合并](https://www.cnblogs.com/wangjiachen666/p/11283499.html)——[MergeTableCell](https://github.com/NLRX/Vue-Components-Library/tree/master/MergeTableCell)
|
||||
|
||||
##### [Vue封装暂无数据占位图组件](https://www.cnblogs.com/wangjiachen666/p/11851699.html)——[Empty](https://github.com/NLRX/Vue-Components-Library/tree/master/Empty)
|
98
SideBar/README.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# 介绍
|
||||
|
||||
这是一个是基于`element-UI`的导航菜单组件基础上,进行了二次封装的菜单组件,该组件以组件递归的方式,实现了可根据从后端接收到的`json`菜单数据,动态渲染多级菜单的功能。
|
||||
|
||||
# 使用方法
|
||||
|
||||
由于该组件是基于`element-UI`进行二次封装的,所以在使用该组件时请务必安装`element-UI`([安装方式猛戳这里](http://element-cn.eleme.io/#/zh-CN/component/installation)),安装好`element-UI`后,只需将该组件文件夹`SideBar`导入到现有项目中即可使用。
|
||||
|
||||
# 工作流程
|
||||
|
||||
组件封装好之后,由父组件调用该组件,父组件先向后端发送请求获取菜单数据,然后将菜单数据传递给封装好的菜单组件,菜单组件通过解析数据,完成菜单渲染。
|
||||
|
||||
# 使用示例
|
||||
|
||||
|
||||
|
||||
```html
|
||||
<template>
|
||||
<div id="app">
|
||||
<Sidebar :menuList="menuList"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Sidebar from './SideBar/SideBar.vue'
|
||||
export default {
|
||||
name: 'app',
|
||||
components: { Sidebar},
|
||||
data() {
|
||||
return {
|
||||
menuList,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
||||
```
|
||||
|
||||
# 选项
|
||||
|
||||
| 属性 | 描述 | 类型 | 是否必须 |
|
||||
| :------: | :------------------: | :---: | :------: |
|
||||
| menuList | 由后端返回的菜单数据 | Array | 是 |
|
||||
|
||||
# 菜单数据格式示例
|
||||
|
||||
```json
|
||||
{
|
||||
"menus" : [
|
||||
{
|
||||
"path": "/func1", //菜单项所对应的路由路径
|
||||
"title": "功能1", //菜单项名称
|
||||
"children":[] //是否有子菜单,若没有,则为[]
|
||||
},
|
||||
{
|
||||
"path": "/func2",
|
||||
"title": "功能2",
|
||||
"children":[]
|
||||
},
|
||||
{
|
||||
"path": "/func3",
|
||||
"title": "功能3",
|
||||
"children": [
|
||||
{
|
||||
"path": "/func31",
|
||||
"title": "功能3-1",
|
||||
"children":[]
|
||||
},
|
||||
{
|
||||
"path": "/func32",
|
||||
"title": "功能3-2",
|
||||
"children":[]
|
||||
},
|
||||
{
|
||||
"path": "/func33",
|
||||
"title": "功能3-3",
|
||||
"children":[]
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
# 效果图
|
||||
|
||||

|
||||
|
||||
# 源代码
|
||||
|
||||
完整代码请戳☞[Vue-Components-Library/SideBar](https://github.com/wangjiachen199366/Vue-Components-Library/tree/master/SideBar)
|
||||
|
74
SideBar/SideBar.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<div class="sidebar-container">
|
||||
<el-scrollbar wrap-class="scrollbar-wrapper">
|
||||
<el-menu
|
||||
router
|
||||
mode="vertical"
|
||||
background-color="#304156"
|
||||
text-color="#bfcbd9"
|
||||
active-text-color="#409EFF">
|
||||
<sidebar-item v-for="menu in menuList" :key="menu.path" :item="menu" />
|
||||
</el-menu>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
import SidebarItem from './SidebarItem'
|
||||
|
||||
export default {
|
||||
name:'Sidebar',
|
||||
components: { SidebarItem },
|
||||
props:{
|
||||
menuList: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.sidebar-container {
|
||||
transition: width 0.28s;
|
||||
width: 180px !important;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
font-size: 0px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1001;
|
||||
overflow: hidden;
|
||||
|
||||
.horizontal-collapse-transition {
|
||||
transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
|
||||
}
|
||||
.el-scrollbar {
|
||||
height: 100%;
|
||||
}
|
||||
.scrollbar-wrapper {
|
||||
overflow-x: hidden!important;
|
||||
.el-scrollbar__view {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.el-scrollbar__bar.is-vertical{
|
||||
right: 0px;
|
||||
}
|
||||
.is-horizontal {
|
||||
display: none;
|
||||
}
|
||||
a {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.el-menu {
|
||||
border: none;
|
||||
height: 100%;
|
||||
width: 100% !important;
|
||||
}
|
||||
.is-active > .el-submenu__title{
|
||||
color: #f4f4f5!important;
|
||||
}
|
||||
}
|
||||
</style>
|
41
SideBar/SidebarItem.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<div v-if="item.children">
|
||||
<template v-if="item.children.length == 0">
|
||||
<el-menu-item :index="item.path">
|
||||
<i class="el-icon-menu"></i>
|
||||
{{item.title}}
|
||||
</el-menu-item>
|
||||
</template>
|
||||
|
||||
<el-submenu v-else :index="item.path">
|
||||
<template slot="title" >
|
||||
<i class="el-icon-menu"></i>
|
||||
{{item.title}}
|
||||
</template>
|
||||
|
||||
<template v-for="child in item.children">
|
||||
<sidebar-item
|
||||
v-if="child.children&&child.children.length>0"
|
||||
:item="child"
|
||||
:key="child.path"/>
|
||||
<el-menu-item v-else :key="child.path" :index="child.path">
|
||||
<i class="el-icon-location"></i>
|
||||
{{child.title}}
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</el-submenu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'SidebarItem',
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
BIN
SideBar/效果图.gif
Normal file
After Width: | Height: | Size: 96 KiB |
39
UploadFile/README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# 介绍
|
||||
|
||||
这是一个是基于`element-UI`的文件上传组件基础上,进行了二次封装,该组件除了可以正常的进行文件上传,还增加了显示文件上传的进度条。
|
||||
|
||||
# 使用方法
|
||||
|
||||
由于该组件是基于`element-UI`的分页组件进行二次封装,所以在使用该组件时请务必安装`element-UI`([安装方式猛戳这里](http://element-cn.eleme.io/#/zh-CN/component/installation)),另外,该组件进行文件上传的操作采用了`axios`,所以也必须先安装好`axios`,安装好上述两个库后,只需将该组件文件夹`UploadFile`导入到现有项目中即可使用。
|
||||
|
||||
# 使用示例
|
||||
|
||||
```html
|
||||
<template>
|
||||
<div id="app">
|
||||
<upload-file :url="/test/"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import UploadFile from './UploadFile'
|
||||
export default {
|
||||
name: 'app',
|
||||
components: { Sidebar},
|
||||
data() {
|
||||
return {
|
||||
url, //文件上传到的目的url
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
# 选项
|
||||
|
||||
| 属性 | 描述 | 类型 | 是否必须 |
|
||||
| :----------: | :--: | :--: | :----------: |
|
||||
| url | 件上传到的目的url | String | 是 |
|
||||
|
||||
# 效果图
|
||||

|
127
UploadFile/index.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<div>
|
||||
<!--文件上传入口-->
|
||||
<div class="uploadfile">
|
||||
<el-upload
|
||||
ref="upload"
|
||||
class="upload-demo"
|
||||
:before-upload="beforeUpload"
|
||||
drag
|
||||
:auto-upload="false"
|
||||
>
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击选择文件</em></div>
|
||||
</el-upload>
|
||||
<el-button style="margin-left: 10px;" size="small" type="success" @click="submitUpload">上传</el-button>
|
||||
</div>
|
||||
<!--遮罩层-->
|
||||
<div class="loading" v-if="loading" >
|
||||
<h4 class="tips">{{tips}}</h4>
|
||||
<!--进度条-->
|
||||
<el-progress type="line" :percentage="percentage" class="progress" :show-text="true"></el-progress>
|
||||
</div>
|
||||
<!--上传完成提示对话框-->
|
||||
<el-dialog
|
||||
title="提示"
|
||||
:visible="dialogVisible"
|
||||
width="30%"
|
||||
:modal-append-to-body='false'
|
||||
>
|
||||
<span>文件上传成功</span>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="ensure">确 定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "UploadFile",
|
||||
data(){
|
||||
return {
|
||||
loading:false,
|
||||
percentage:0,
|
||||
tips:'',
|
||||
dialogVisible:false
|
||||
}
|
||||
},
|
||||
props:['url'],
|
||||
methods:{
|
||||
beforeUpload(file){
|
||||
let fd = new FormData();
|
||||
fd.append('file', file);
|
||||
let config = {
|
||||
onUploadProgress: progressEvent => {
|
||||
//progressEvent.loaded:已上传文件大小
|
||||
//progressEvent.total:被上传文件的总大小
|
||||
let complete = (progressEvent.loaded / progressEvent.total ).toFixed(2) * 100 ;
|
||||
this.percentage = complete;
|
||||
if (this.percentage >= 100){
|
||||
this.dialogVisible = true
|
||||
}
|
||||
},
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
};
|
||||
this.$axios.post(this.url,fd,config)
|
||||
.then((res)=>{
|
||||
//文件上传成功的回调
|
||||
})
|
||||
.catch((err)=>{
|
||||
//异常捕获
|
||||
})
|
||||
},
|
||||
submitUpload(){
|
||||
this.loading = true;
|
||||
this.tips = '正在上传中。。。';
|
||||
this.$refs.upload.submit();
|
||||
},
|
||||
ensure(){
|
||||
this.dialogVisible = false;
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.uploadfile{
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-left: -100px;
|
||||
margin-top: -100px;
|
||||
}
|
||||
.loading{
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: black;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.progress{
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-left: -100px;
|
||||
margin-top: -100px;
|
||||
}
|
||||
.tips{
|
||||
color: #409eff;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-left: -100px;
|
||||
margin-top: -150px;
|
||||
}
|
||||
|
||||
</style>
|
BIN
UploadFile/效果图.gif
Normal file
After Width: | Height: | Size: 180 KiB |
60
VueHoverMask/README.md
Normal file
@@ -0,0 +1,60 @@
|
||||
|
||||
|
||||
# 介绍
|
||||
|
||||
当鼠标hover 上元素时,给元素加一层遮罩层。
|
||||
|
||||
# 效果图
|
||||
|
||||

|
||||
|
||||
# 使用
|
||||
|
||||
```js
|
||||
import VueHoverMask from 'vue-hover-mask'
|
||||
export default {
|
||||
components: {
|
||||
VueHoverMask
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# 示例
|
||||
|
||||
```html
|
||||
<template>
|
||||
<div id="app">
|
||||
<vue-hover-mask @click="handleClick">
|
||||
<!-- 默认插槽 -->
|
||||
<img src="https://cn.vuejs.org/images/logo.png" alt="" srcset="">
|
||||
<!-- action插槽 -->
|
||||
<template v-slot:action>
|
||||
<i class="iconfont icon-bianji-copy">编辑</i>
|
||||
</template>
|
||||
</vue-hover-mask>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import VueHoverMask from './components/VueHoverMask'
|
||||
export default {
|
||||
name: 'app',
|
||||
components:{ VueHoverMask },
|
||||
methods: {
|
||||
handleClick() {
|
||||
console.log('click')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
@import url("//at.alicdn.com/t/font_1262845_52b6h42svd7.css");
|
||||
.iconfont {
|
||||
font-size: 25px;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
# 组件代码
|
||||
|
||||
完整代码请戳☞[Vue-Components-Library/VueHoverMask](https://github.com/wangjiachen199366/Vue-Components-Library/tree/master/VueHoverMask)
|
61
VueHoverMask/index.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div class="vue-hover-mask">
|
||||
<slot></slot>
|
||||
<span
|
||||
@click="handleClick"
|
||||
class="vue-hover-mask_action"
|
||||
>
|
||||
<slot name="action"></slot>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'VueHoverMask',
|
||||
methods: {
|
||||
handleClick () {
|
||||
this.$emit('click')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.vue-hover-mask {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
line-height: 1;
|
||||
background-color: #fff;
|
||||
border-radius: 6px;
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
font-size: 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.vue-hover-mask_action {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
opacity: 0;
|
||||
font-size: 20px;
|
||||
z-index: 1;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
.vue-hover-mask_action::after {
|
||||
display: inline-block;
|
||||
content: "";
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.vue-hover-mask_action:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
BIN
VueHoverMask/效果图.gif
Normal file
After Width: | Height: | Size: 105 KiB |
60
nprogresBar/README.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# 1.前言
|
||||
|
||||
在平常浏览网页时,我们会注意到在有的网站中,当点击页面中的链接进行路由跳转时,页面顶部会有一个进度条,用来标示页面跳转的进度(如下图所示)。虽然实际用处不大,但是对用户来说,有个进度条会大大减轻用户的等待压力,提升用户体验。本篇文章就来教你如何在Vue项目中实现这样的进度条。
|
||||
|
||||

|
||||
|
||||
# 2.安装Nprogress
|
||||
|
||||
虽然我们也可以自己手动实现这样的功能,但是,`nprogress.js`已经帮我们把进度条的样式呀,功能呀都已经封装的很好了,既然有现成的轮子,我们就直接使用轮子就好啦!
|
||||
|
||||
```shell
|
||||
npm install nprogress -S
|
||||
```
|
||||
|
||||
# 3.在router.js中引入Nprogress
|
||||
|
||||
由于我们需要将`Nprogress`绑定在路由钩子上,所以我们直接在路由文件`router.js`中引入`Nprogress`。
|
||||
|
||||
```javascript
|
||||
import NProgress from 'nprogress'
|
||||
import 'nprogress/nprogress.css'// nprogress样式文件
|
||||
```
|
||||
|
||||
# 4.绑定路由钩子
|
||||
|
||||
我们想要的效果是:当路由开始跳转时加载进度条,当路由跳转完毕时进度条加载完毕。幸运的是,在Vue中刚好为我们提供了路由开始跳转和结束跳转的钩子,我们只需在引入`Nprogress`之后,将其绑定在路由钩子上即可。代码如下:
|
||||
|
||||
```javascript
|
||||
//当路由开始跳转时
|
||||
router.beforeEach((to, from , next) => {
|
||||
// 开启进度条
|
||||
NProgress.start();
|
||||
// 这个一定要加,没有next()页面不会跳转的。这部分还不清楚的去翻一下官网就明白了
|
||||
next();
|
||||
});
|
||||
//当路由跳转结束后
|
||||
router.afterEach(() => {
|
||||
// 关闭进度条
|
||||
NProgress.done()
|
||||
})
|
||||
```
|
||||
|
||||
# 5.效果图
|
||||
|
||||

|
||||
|
||||
# 6.个性化配置
|
||||
|
||||
当然如果你对默认的进度条外观样式不满意,`Nprogress`还提供了个性化配置外观。
|
||||
|
||||
```javascript
|
||||
NProgress.configure({
|
||||
easing: 'ease', // 动画方式
|
||||
speed: 500, // 递增进度条的速度
|
||||
showSpinner: false, // 是否显示加载ico
|
||||
trickleSpeed: 200, // 自动递增间隔
|
||||
minimum: 0.3 // 初始化时的最小百分比
|
||||
})
|
||||
```
|
||||
|
38
nprogresBar/router.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import Vue from 'vue'
|
||||
import Router from 'vue-router'
|
||||
import NProgress from 'nprogress' // progress bar
|
||||
import 'nprogress/nprogress.css'// progress bar style
|
||||
|
||||
Vue.use(Router)
|
||||
|
||||
// 个性化配置进度条外观
|
||||
NProgress.configure({
|
||||
easing: 'ease', // 动画方式
|
||||
speed: 500, // 递增进度条的速度
|
||||
showSpinner: false, // 是否显示加载ico
|
||||
trickleSpeed: 200, // 自动递增间隔
|
||||
minimum: 0.3 // 初始化时的最小百分比
|
||||
})
|
||||
|
||||
const router = new Router({
|
||||
routes: [
|
||||
{ path: '/', redirect: '/index' },
|
||||
{ path: '/login', name: 'login', component: Login },
|
||||
]
|
||||
})
|
||||
|
||||
// 添加路由守卫
|
||||
router.beforeEach((to, from, next) => {
|
||||
NProgress.start()
|
||||
if (to.path == "/login") {
|
||||
next();
|
||||
NProgress.done()
|
||||
} else {
|
||||
isLogin ? next() : next("/login");
|
||||
NProgress.done()
|
||||
}
|
||||
})
|
||||
router.afterEach(() => {
|
||||
NProgress.done()
|
||||
})
|
||||
export default router;
|