分页 Pagination
<!--
组件名:分页 Pagination
属性:
total <Number> 总条数
page <Number> 当前页码
limit <Number> 每页条数
autoScroll <Boolean> 点击分页是否自动滚动到最上面
hidden <Boolean> 是否隐藏分页组件
background <Boolean> 是否要分页背景
事件:
pagination({ page, limit }) 分页改变触发事件
-->
<template>
<div :class="{'hidden':hidden}" class="pagination-container">
<el-pagination
:background="background"
:current-page.sync="currentPage"
:page-size.sync="pageSize"
:layout="layout"
:page-sizes="pageSizes"
:total="total"
v-bind="$attrs"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</template>
<script>
import { scrollTo } from '@/utils/scroll-to'
export default {
name: 'BuPagination',
props: {
total: {
type: Number,
default: 0
},
page: {
type: Number,
default: 1
},
limit: {
type: Number,
default: 20
},
pageSizes: {
type: Array,
default() {
return [10, 20, 30, 50]
}
},
layout: {
type: String,
default: 'total, sizes, prev, pager, next, jumper'
},
background: {
type: Boolean,
default: true
},
autoScroll: {
type: Boolean,
default: true
},
hidden: {
type: Boolean,
default: false
}
},
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 })
if (this.autoScroll) {
scrollTo(0, 800)
}
},
handleCurrentChange(val) {
this.$emit('pagination', { page: val, limit: this.pageSize })
if (this.autoScroll) {
scrollTo(0, 800)
}
}
}
}
</script>
<style scoped>
.pagination-container {
background: #fff;
padding: 32px 16px;
text-align: center;
}
.pagination-container.hidden {
display: none;
}
</style>
选择器 Select
<!--
组件名:下拉选择 Select
属性:
options <Array> 下拉选择列表项
placeholder <string> 无选择时显示的文字
labelText <string> 对于下拉列表的 label 值
valueText <string> 对于下拉列表的 value 值
selectValue <string> 选中的值用于回显
remote <object> select 使用请求的配置项
url <string> 请求的url
body <object> 请求的参数
multiple <boolean> 是否多选
filterFunction <function> 处理请求的结果, 必须 return 最终结果
事件:
remote(val) 与element用法一致
loadMore 加载更多的事件
change(value, label) 选择下拉列表项后触发事件
beforeSend 在请求之前的处理操作
-->
<!--:remote="remoteable"-->
<template>
<el-select
class="bu-select" :style="{width: width + 'px'}" v-model="value" :clearable="true" @change="handleChange" :placeholder="placeholder"
:multiple="multiple"
:filterable="filterable" :filter-method="remoteSearch" :loading="loading"
v-loadmore="loadMore"
>
<el-option v-for='item in optionList' :key="JSON.stringify(item)" :label='item[labelText]'
:value='item[valueText]'></el-option>
</el-select>
</template>
<script>
import { isFunction, isNumber } from '../../utils/dataType'
export default {
name: 'BuSelect',
props: {
width: {
type: Number,
default: 130
},
options: {
type: Array,
default: () => []
},
placeholder: {
type: String,
default: ''
},
labelText: {
type: String,
default: 'label'
},
valueText: {
type: String,
default: 'value'
},
remote: {
type: Object,
default: () => {
return { url: '', body: {} }
}
},
multiple: {
type: Boolean,
default: false
},
filterFunction: {
type: Function,
default: (res) => {
return res
}
},
selectValue: {
type: String,
default: ''
}
},
data () {
return {
optionList: [],
value: '',
filterable: false,
remoteable: false,
loading: false,
page: 1,
limit: 20,
searchKey: ''
}
},
watch: {
selectValue: {
handler (val) {
this.value = val
},
immediate: true
},
remote: {
handler (val) {
if (val.url) {
this.filterable = true
this.remoteable = true
}
},
immediate: true
},
options: {
handler (val) {
this.optionList = val
},
immediate: true
},
},
mounted () {
this.remoteSearch()
},
methods: {
remoteSearch (val) {
this.searchKey = val ? val : ''
if (!this.remote.url) {
return
}
this.optionList = []
this.page = 1
this.getData()
this.$emit('remote', val)
},
loadMore () {
this.page++
this.getData()
this.$emit('loadMore')
},
handleChange () {
let label = ''
let item = {}
for (let i = 0; i < this.optionList.length; i++) {
if (this.optionList[i].value === this.value) {
label = this.optionList[i].label
item = this.optionList[i].item
break
}
}
this.$emit('change', this.value, label, item)
},
getData (p) {
this.$emit('beforeSend')
// 若 p === 1 则刷新数据
if (isNumber(p)){
this.page = p
if (p === 1) {
this.value = ''
this.optionList = []
this.remote.body['SEARCH_KEY'] = ''
}
}
let { url, body } = this.remote
if (!url) {
throw new Error('url can not be null.')
return
}
const page = { 'PN': this.page, 'PAGE_SIZE': this.limit }
body = Object.assign({}, body, page, { 'SEARCH_KEY': this.searchKey ? this.searchKey : '' })
this.$axios({ method: 'POST', url, body }, res => {
if (this.filterFunction && isFunction(this.filterFunction)) {
const list = Object.freeze(this.filterFunction(res))
this.optionList.push(...list)
}
})
}
}
}
</script>
<style scoped lang="scss">
.bu-select {
/*width: 130px;*/
}
</style>
表格 Table
<!--
组件名:表格 Table
属性:
data <Array> 表格数据
height <String|Number> 表格高度
setting <Array> 表格配置项
label <String> 表格列名
prop <String> data中对应的要渲染的属性名
formatter <Function> 格式化最终值函数
img <Boolean> 是否以图片的形式显示
btn <String|Array> 按钮的值 格式 "详情" 或 ["详情", "删除"]
dropdown(scope) <Function> 下拉列表 必须返回一个数组 格式例如 [{command: "1", text: "删除"}]
command <String> 命令值
text <String> 文本
btnGroup <Boolean> 是否显示btn和dropdown
sortable <Boolean> 是否显示排序
slot <String> 插槽的名称,拥有这个属性即可使用对应的插槽,例如:
<bu-table :data="tableData" :setting="setting" @command="handleCommand">
<template slot="name" slot-scope="scope">
<div>scope.row['name']</div>
</template>
</bu-table>
事件:
operation(index, val, item) 表格操作按钮事件
command(command, template, item) 表格下拉选择列表事件
-->
<template>
<el-table :data="tableData" stripe @sort-change="handleSort">
<el-table-column
v-for="item in setting"
:key="item.label"
:label="item.label"
:sortable="item.sortable"
:prop="item.prop"
:width="item.width"
:min-width="item.minWidth"
:fixed="item.fixed"
:align="item.align"
>
<template slot-scope="scope">
<slot v-if="item.slot" :name="item.slot" :row="scope.row"></slot>
<div v-else-if="item.img">
<!-- 可换成自己的图片组件 -->
<buImage :src="scope.row[item.prop] | image"></buImage>
</div>
<div v-else-if="item.btnGroup">
<el-dropdown v-if="item.dropdown" @command="handleCommand" trigger="click">
<span class="el-dropdown-link">
<el-button type="text" icon="el-icon-more" style="color: #F56C6C;"></el-button>
</span>
<el-dropdown-menu slot="dropdown" class="w100">
<el-dropdown-item v-for="(item, index) in item.dropdown(scope)" :key="index" :command="item.command" :row="scope.row">{{item.text}}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<template v-if="isString(item.btn)">
<el-button type="text" @click="handleOperation(0, item.btn, scope.row)">{{item.btn}}</el-button>
</template>
<template v-if="isArray(item.btn)">
<el-button v-for="(btn, index) in item.btn" :key="btn" type="text" @click="handleOperation(index, btn, scope.row)">{{ btn }}</el-button>
</template>
</div>
<div v-else>
{{ typeof item.formatter === 'function' ? item.formatter(scope.row[item.prop]) : scope.row[item.prop] }}
</div>
</template>
</el-table-column>
</el-table>
</template>
<script>
import { isArray, isString } from '../../utils/dataType'
export default {
name: 'BuTable',
components: {
},
props: {
/**
* 表格数据
*/
data: {
type: Array,
default: () => []
},
/**
* 表格配置
*/
setting: {
type: Array,
default: () => []
},
height: {
type: String | Number
}
},
data () {
return {
tableData: [],
cacheData: []
}
},
watch: {
data: {
handler (val) {
this.tableData = val
this.cacheData = this.deepClone(this.data)
},
immediate: true,
deep: true
}
},
methods: {
isArray(obj) {
return isArray(obj)
},
isString(obj) {
return isString(obj)
},
handleOperation(index, val, item) {
this.$emit('operation', index, val, item)
},
handleCommand(command, template, item) {
this.$emit('command', command, template, item)
},
bubbleSort (arr, prop, isDesc) {
let len = arr.length
for (let i = 0; i < len - 1; i++) {
for (let j = 0; j < len - 1 - i; j++) {
if (Number(arr[j][prop]) > Number(arr[j + 1][prop])) {
let temp = arr[j]
arr[j] = arr[j + 1]
arr[j + 1] = temp
}
}
}
if (isDesc) {
arr.reverse()
}
return arr
},
deepClone(obj = {}) {
// 值类型的情况下直接返回
// obj 是 null,或者不是对象也不是数组,就直接返回
if (typeof obj !== 'object' || obj == null) {
return obj
}
// 初始化返回结果,是数组就定义为数组,是对象就定义为对象
let result
if (obj instanceof Array) {
result = []
} else {
result = {}
}
for (const key in obj) {
// 判断 key 是否是自身的属性
// eslint-disable-next-line no-prototype-builtins
if (obj.hasOwnProperty(key)) {
// 保证不是原型上的属性
result[key] = this.deepClone(obj[key])
}
}
return result
},
handleSort ({ prop, order }) {
if (order === 'ascending') {
this.tableData = this.deepClone(this.bubbleSort(this.cacheData, prop))
} else if (order === 'descending') {
this.tableData = this.deepClone(this.bubbleSort(this.cacheData, prop, true))
} else {
this.tableData = this.deepClone(this.cacheData)
}
}
}
}
</script>
<style scoped lang="scss">
</style>
容器 Container
<!--
组件名:表格 Container
属性:
url <String> 请求表格的url
filter <Object> 请求表格的参数
data <Array> 表格数据
height <String|Number> 表格高度
setting <Array> 表格配置项 参数请参考BuTable组件的setting
total <Number> 总条数
page <Number> 当前页码
limit <Number> 每页条数
autoScroll <Boolean> 点击分页是否自动滚动到最上面
hiddenPagination <Boolean> 是否隐藏分页组件
background <Boolean> 是否要分页背景
excel <object> 导出表格配置
url <string> 导出表格的的url,若无url,则不显示导出按钮
body <object> 请求的参数
事件:
operation(index, val, item) 表格操作按钮事件
command(command, template, item) 表格下拉选择列表事件
pagination({ page, limit }) 分页改变触发事件
beforeExport() 导出表格之前触发
-->
<template>
<div class="bu-container">
<div>
<slot name="header"></slot>
<el-button v-if="excel" type="primary" class="fl-right" @click="exportExcel">导出excel</el-button>
</div>
<div>
<bu-table :data="tableData" :setting="setting" @operation="handleOperation" @command="handleCommand"></bu-table>
</div>
<bu-pagination
:total="p_total"
:page="p_page"
:limit="p_limit"
:page-sizes="pageSizes"
:layout="layout"
:background="background"
:auto-scroll="autoScroll"
:hidden="hiddenPagination"
@pagination="handlePagination"
/>
<div>
<slot name="footer"></slot>
</div>
</div>
</template>
<script>
import BuTable from '../BuTable/index'
import BuPagination from '../BuPagination/index'
import request from '../../utils/request'
export default {
name: 'BuContainer',
components: {
BuTable,
BuPagination
},
props: {
url: {
type: String,
default: ''
},
filter: {
type: Object,
default: () => {
return {}
}
},
/**
* 表格数据
*/
data: {
type: Array,
default: () => []
},
/**
* 表格配置
*/
setting: {
type: Array,
default: () => []
},
height: {
type: String | Number
},
excel: {
type: Object | String,
default: ''
},
total: {
type: Number,
default: 0
},
page: {
type: Number,
default: 1
},
limit: {
type: Number,
default: 20
},
pageSizes: {
type: Array,
default () {
return [10, 20, 30, 50]
}
},
layout: {
type: String,
default: 'total, prev, pager, next, jumper'
},
background: {
type: Boolean,
default: true
},
autoScroll: {
type: Boolean,
default: true
},
hiddenPagination: {
type: Boolean,
default: false
}
},
data () {
return {
tableData: [],
p_limit: 20,
p_page: 1,
p_total: 0
}
},
watch: {
data: {
handler (val) {
this.tableData = val
},
immediate: true
},
url: {
handler (val) {
if (val) {
this.fetchData()
}
},
immediate: true
},
limit: {
handler (val) {
this.p_limit = val
},
immediate: true
},
page: {
handler (val) {
this.p_page = val
},
immediate: true
}
},
methods: {
fetchData (filterData, type) {
if (type === 'init') {
this.p_page = 1
}
let data = { 'PN': this.p_page, 'PAGE_SIZE': this.p_limit }
if (filterData) {
data = Object.assign({}, data, filterData)
} else {
data = Object.assign({}, data, this.filter)
}
request({
url: this.url,
data
}).then(res => {
this.tableData = res.data
res.extraData && res.extraData.page && (this.p_total = res.extraData.page.querySize)
})
},
handleOperation (index, val, item) {
this.$emit('operation', index, val, item)
},
handleCommand (command, template, item) {
this.$emit('command', command, template, item)
},
exportExcel (excel) {
this.$emit("beforeExport")
if (excel.url) {
this.excel = excel
}
const { url, body = {} } = this.excel
if (!url) {
return
}
this.$axios({ method: 'POST', type: 'form', url, body }, res => {
let url = res.extraData['URL']
if (url) {
window.open(url)
} else if (res.code === 400 || !url) {
this.$message({
message: '导出失败',
type: 'warn'
})
}
})
},
handlePagination ({ page, limit }) {
this.p_page = page
this.fetchData()
this.$emit('pagination', { page, limit })
},
}
}
</script>
<style scoped lang="scss">
.fl-right {
float: right;
}
.bu-container {
padding: 30px 15px;
}
</style>