分页 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>