分页 Pagination

  1. <!--
  2. 组件名:分页 Pagination
  3. 属性:
  4. total <Number> 总条数
  5. page <Number> 当前页码
  6. limit <Number> 每页条数
  7. autoScroll <Boolean> 点击分页是否自动滚动到最上面
  8. hidden <Boolean> 是否隐藏分页组件
  9. background <Boolean> 是否要分页背景
  10. 事件:
  11. pagination({ page, limit }) 分页改变触发事件
  12. -->
  13. <template>
  14. <div :class="{'hidden':hidden}" class="pagination-container">
  15. <el-pagination
  16. :background="background"
  17. :current-page.sync="currentPage"
  18. :page-size.sync="pageSize"
  19. :layout="layout"
  20. :page-sizes="pageSizes"
  21. :total="total"
  22. v-bind="$attrs"
  23. @size-change="handleSizeChange"
  24. @current-change="handleCurrentChange"
  25. />
  26. </div>
  27. </template>
  28. <script>
  29. import { scrollTo } from '@/utils/scroll-to'
  30. export default {
  31. name: 'BuPagination',
  32. props: {
  33. total: {
  34. type: Number,
  35. default: 0
  36. },
  37. page: {
  38. type: Number,
  39. default: 1
  40. },
  41. limit: {
  42. type: Number,
  43. default: 20
  44. },
  45. pageSizes: {
  46. type: Array,
  47. default() {
  48. return [10, 20, 30, 50]
  49. }
  50. },
  51. layout: {
  52. type: String,
  53. default: 'total, sizes, prev, pager, next, jumper'
  54. },
  55. background: {
  56. type: Boolean,
  57. default: true
  58. },
  59. autoScroll: {
  60. type: Boolean,
  61. default: true
  62. },
  63. hidden: {
  64. type: Boolean,
  65. default: false
  66. }
  67. },
  68. computed: {
  69. currentPage: {
  70. get() {
  71. return this.page
  72. },
  73. set(val) {
  74. this.$emit('update:page', val)
  75. }
  76. },
  77. pageSize: {
  78. get() {
  79. return this.limit
  80. },
  81. set(val) {
  82. this.$emit('update:limit', val)
  83. }
  84. }
  85. },
  86. methods: {
  87. handleSizeChange(val) {
  88. this.$emit('pagination', { page: this.currentPage, limit: val })
  89. if (this.autoScroll) {
  90. scrollTo(0, 800)
  91. }
  92. },
  93. handleCurrentChange(val) {
  94. this.$emit('pagination', { page: val, limit: this.pageSize })
  95. if (this.autoScroll) {
  96. scrollTo(0, 800)
  97. }
  98. }
  99. }
  100. }
  101. </script>
  102. <style scoped>
  103. .pagination-container {
  104. background: #fff;
  105. padding: 32px 16px;
  106. text-align: center;
  107. }
  108. .pagination-container.hidden {
  109. display: none;
  110. }
  111. </style>

选择器 Select

  1. <!--
  2. 组件名:下拉选择 Select
  3. 属性:
  4. options <Array> 下拉选择列表项
  5. placeholder <string> 无选择时显示的文字
  6. labelText <string> 对于下拉列表的 label 值
  7. valueText <string> 对于下拉列表的 value 值
  8. selectValue <string> 选中的值用于回显
  9. remote <object> select 使用请求的配置项
  10. url <string> 请求的url
  11. body <object> 请求的参数
  12. multiple <boolean> 是否多选
  13. filterFunction <function> 处理请求的结果, 必须 return 最终结果
  14. 事件:
  15. remote(val) 与element用法一致
  16. loadMore 加载更多的事件
  17. change(value, label) 选择下拉列表项后触发事件
  18. beforeSend 在请求之前的处理操作
  19. -->
  20. <!--:remote="remoteable"-->
  21. <template>
  22. <el-select
  23. class="bu-select" :style="{width: width + 'px'}" v-model="value" :clearable="true" @change="handleChange" :placeholder="placeholder"
  24. :multiple="multiple"
  25. :filterable="filterable" :filter-method="remoteSearch" :loading="loading"
  26. v-loadmore="loadMore"
  27. >
  28. <el-option v-for='item in optionList' :key="JSON.stringify(item)" :label='item[labelText]'
  29. :value='item[valueText]'></el-option>
  30. </el-select>
  31. </template>
  32. <script>
  33. import { isFunction, isNumber } from '../../utils/dataType'
  34. export default {
  35. name: 'BuSelect',
  36. props: {
  37. width: {
  38. type: Number,
  39. default: 130
  40. },
  41. options: {
  42. type: Array,
  43. default: () => []
  44. },
  45. placeholder: {
  46. type: String,
  47. default: ''
  48. },
  49. labelText: {
  50. type: String,
  51. default: 'label'
  52. },
  53. valueText: {
  54. type: String,
  55. default: 'value'
  56. },
  57. remote: {
  58. type: Object,
  59. default: () => {
  60. return { url: '', body: {} }
  61. }
  62. },
  63. multiple: {
  64. type: Boolean,
  65. default: false
  66. },
  67. filterFunction: {
  68. type: Function,
  69. default: (res) => {
  70. return res
  71. }
  72. },
  73. selectValue: {
  74. type: String,
  75. default: ''
  76. }
  77. },
  78. data () {
  79. return {
  80. optionList: [],
  81. value: '',
  82. filterable: false,
  83. remoteable: false,
  84. loading: false,
  85. page: 1,
  86. limit: 20,
  87. searchKey: ''
  88. }
  89. },
  90. watch: {
  91. selectValue: {
  92. handler (val) {
  93. this.value = val
  94. },
  95. immediate: true
  96. },
  97. remote: {
  98. handler (val) {
  99. if (val.url) {
  100. this.filterable = true
  101. this.remoteable = true
  102. }
  103. },
  104. immediate: true
  105. },
  106. options: {
  107. handler (val) {
  108. this.optionList = val
  109. },
  110. immediate: true
  111. },
  112. },
  113. mounted () {
  114. this.remoteSearch()
  115. },
  116. methods: {
  117. remoteSearch (val) {
  118. this.searchKey = val ? val : ''
  119. if (!this.remote.url) {
  120. return
  121. }
  122. this.optionList = []
  123. this.page = 1
  124. this.getData()
  125. this.$emit('remote', val)
  126. },
  127. loadMore () {
  128. this.page++
  129. this.getData()
  130. this.$emit('loadMore')
  131. },
  132. handleChange () {
  133. let label = ''
  134. let item = {}
  135. for (let i = 0; i < this.optionList.length; i++) {
  136. if (this.optionList[i].value === this.value) {
  137. label = this.optionList[i].label
  138. item = this.optionList[i].item
  139. break
  140. }
  141. }
  142. this.$emit('change', this.value, label, item)
  143. },
  144. getData (p) {
  145. this.$emit('beforeSend')
  146. // 若 p === 1 则刷新数据
  147. if (isNumber(p)){
  148. this.page = p
  149. if (p === 1) {
  150. this.value = ''
  151. this.optionList = []
  152. this.remote.body['SEARCH_KEY'] = ''
  153. }
  154. }
  155. let { url, body } = this.remote
  156. if (!url) {
  157. throw new Error('url can not be null.')
  158. return
  159. }
  160. const page = { 'PN': this.page, 'PAGE_SIZE': this.limit }
  161. body = Object.assign({}, body, page, { 'SEARCH_KEY': this.searchKey ? this.searchKey : '' })
  162. this.$axios({ method: 'POST', url, body }, res => {
  163. if (this.filterFunction && isFunction(this.filterFunction)) {
  164. const list = Object.freeze(this.filterFunction(res))
  165. this.optionList.push(...list)
  166. }
  167. })
  168. }
  169. }
  170. }
  171. </script>
  172. <style scoped lang="scss">
  173. .bu-select {
  174. /*width: 130px;*/
  175. }
  176. </style>

表格 Table

  1. <!--
  2. 组件名:表格 Table
  3. 属性:
  4. data <Array> 表格数据
  5. height <String|Number> 表格高度
  6. setting <Array> 表格配置项
  7. label <String> 表格列名
  8. prop <String> data中对应的要渲染的属性名
  9. formatter <Function> 格式化最终值函数
  10. img <Boolean> 是否以图片的形式显示
  11. btn <String|Array> 按钮的值 格式 "详情" 或 ["详情", "删除"]
  12. dropdown(scope) <Function> 下拉列表 必须返回一个数组 格式例如 [{command: "1", text: "删除"}]
  13. command <String> 命令值
  14. text <String> 文本
  15. btnGroup <Boolean> 是否显示btn和dropdown
  16. sortable <Boolean> 是否显示排序
  17. slot <String> 插槽的名称,拥有这个属性即可使用对应的插槽,例如:
  18. <bu-table :data="tableData" :setting="setting" @command="handleCommand">
  19. <template slot="name" slot-scope="scope">
  20. <div>scope.row['name']</div>
  21. </template>
  22. </bu-table>
  23. 事件:
  24. operation(index, val, item) 表格操作按钮事件
  25. command(command, template, item) 表格下拉选择列表事件
  26. -->
  27. <template>
  28. <el-table :data="tableData" stripe @sort-change="handleSort">
  29. <el-table-column
  30. v-for="item in setting"
  31. :key="item.label"
  32. :label="item.label"
  33. :sortable="item.sortable"
  34. :prop="item.prop"
  35. :width="item.width"
  36. :min-width="item.minWidth"
  37. :fixed="item.fixed"
  38. :align="item.align"
  39. >
  40. <template slot-scope="scope">
  41. <slot v-if="item.slot" :name="item.slot" :row="scope.row"></slot>
  42. <div v-else-if="item.img">
  43. <!-- 可换成自己的图片组件 -->
  44. <buImage :src="scope.row[item.prop] | image"></buImage>
  45. </div>
  46. <div v-else-if="item.btnGroup">
  47. <el-dropdown v-if="item.dropdown" @command="handleCommand" trigger="click">
  48. <span class="el-dropdown-link">
  49. <el-button type="text" icon="el-icon-more" style="color: #F56C6C;"></el-button>
  50. </span>
  51. <el-dropdown-menu slot="dropdown" class="w100">
  52. <el-dropdown-item v-for="(item, index) in item.dropdown(scope)" :key="index" :command="item.command" :row="scope.row">{{item.text}}</el-dropdown-item>
  53. </el-dropdown-menu>
  54. </el-dropdown>
  55. <template v-if="isString(item.btn)">
  56. <el-button type="text" @click="handleOperation(0, item.btn, scope.row)">{{item.btn}}</el-button>
  57. </template>
  58. <template v-if="isArray(item.btn)">
  59. <el-button v-for="(btn, index) in item.btn" :key="btn" type="text" @click="handleOperation(index, btn, scope.row)">{{ btn }}</el-button>
  60. </template>
  61. </div>
  62. <div v-else>
  63. {{ typeof item.formatter === 'function' ? item.formatter(scope.row[item.prop]) : scope.row[item.prop] }}
  64. </div>
  65. </template>
  66. </el-table-column>
  67. </el-table>
  68. </template>
  69. <script>
  70. import { isArray, isString } from '../../utils/dataType'
  71. export default {
  72. name: 'BuTable',
  73. components: {
  74. },
  75. props: {
  76. /**
  77. * 表格数据
  78. */
  79. data: {
  80. type: Array,
  81. default: () => []
  82. },
  83. /**
  84. * 表格配置
  85. */
  86. setting: {
  87. type: Array,
  88. default: () => []
  89. },
  90. height: {
  91. type: String | Number
  92. }
  93. },
  94. data () {
  95. return {
  96. tableData: [],
  97. cacheData: []
  98. }
  99. },
  100. watch: {
  101. data: {
  102. handler (val) {
  103. this.tableData = val
  104. this.cacheData = this.deepClone(this.data)
  105. },
  106. immediate: true,
  107. deep: true
  108. }
  109. },
  110. methods: {
  111. isArray(obj) {
  112. return isArray(obj)
  113. },
  114. isString(obj) {
  115. return isString(obj)
  116. },
  117. handleOperation(index, val, item) {
  118. this.$emit('operation', index, val, item)
  119. },
  120. handleCommand(command, template, item) {
  121. this.$emit('command', command, template, item)
  122. },
  123. bubbleSort (arr, prop, isDesc) {
  124. let len = arr.length
  125. for (let i = 0; i < len - 1; i++) {
  126. for (let j = 0; j < len - 1 - i; j++) {
  127. if (Number(arr[j][prop]) > Number(arr[j + 1][prop])) {
  128. let temp = arr[j]
  129. arr[j] = arr[j + 1]
  130. arr[j + 1] = temp
  131. }
  132. }
  133. }
  134. if (isDesc) {
  135. arr.reverse()
  136. }
  137. return arr
  138. },
  139. deepClone(obj = {}) {
  140. // 值类型的情况下直接返回
  141. // obj 是 null,或者不是对象也不是数组,就直接返回
  142. if (typeof obj !== 'object' || obj == null) {
  143. return obj
  144. }
  145. // 初始化返回结果,是数组就定义为数组,是对象就定义为对象
  146. let result
  147. if (obj instanceof Array) {
  148. result = []
  149. } else {
  150. result = {}
  151. }
  152. for (const key in obj) {
  153. // 判断 key 是否是自身的属性
  154. // eslint-disable-next-line no-prototype-builtins
  155. if (obj.hasOwnProperty(key)) {
  156. // 保证不是原型上的属性
  157. result[key] = this.deepClone(obj[key])
  158. }
  159. }
  160. return result
  161. },
  162. handleSort ({ prop, order }) {
  163. if (order === 'ascending') {
  164. this.tableData = this.deepClone(this.bubbleSort(this.cacheData, prop))
  165. } else if (order === 'descending') {
  166. this.tableData = this.deepClone(this.bubbleSort(this.cacheData, prop, true))
  167. } else {
  168. this.tableData = this.deepClone(this.cacheData)
  169. }
  170. }
  171. }
  172. }
  173. </script>
  174. <style scoped lang="scss">
  175. </style>

容器 Container

  1. <!--
  2. 组件名:表格 Container
  3. 属性:
  4. url <String> 请求表格的url
  5. filter <Object> 请求表格的参数
  6. data <Array> 表格数据
  7. height <String|Number> 表格高度
  8. setting <Array> 表格配置项 参数请参考BuTable组件的setting
  9. total <Number> 总条数
  10. page <Number> 当前页码
  11. limit <Number> 每页条数
  12. autoScroll <Boolean> 点击分页是否自动滚动到最上面
  13. hiddenPagination <Boolean> 是否隐藏分页组件
  14. background <Boolean> 是否要分页背景
  15. excel <object> 导出表格配置
  16. url <string> 导出表格的的url,若无url,则不显示导出按钮
  17. body <object> 请求的参数
  18. 事件:
  19. operation(index, val, item) 表格操作按钮事件
  20. command(command, template, item) 表格下拉选择列表事件
  21. pagination({ page, limit }) 分页改变触发事件
  22. beforeExport() 导出表格之前触发
  23. -->
  24. <template>
  25. <div class="bu-container">
  26. <div>
  27. <slot name="header"></slot>
  28. <el-button v-if="excel" type="primary" class="fl-right" @click="exportExcel">导出excel</el-button>
  29. </div>
  30. <div>
  31. <bu-table :data="tableData" :setting="setting" @operation="handleOperation" @command="handleCommand"></bu-table>
  32. </div>
  33. <bu-pagination
  34. :total="p_total"
  35. :page="p_page"
  36. :limit="p_limit"
  37. :page-sizes="pageSizes"
  38. :layout="layout"
  39. :background="background"
  40. :auto-scroll="autoScroll"
  41. :hidden="hiddenPagination"
  42. @pagination="handlePagination"
  43. />
  44. <div>
  45. <slot name="footer"></slot>
  46. </div>
  47. </div>
  48. </template>
  49. <script>
  50. import BuTable from '../BuTable/index'
  51. import BuPagination from '../BuPagination/index'
  52. import request from '../../utils/request'
  53. export default {
  54. name: 'BuContainer',
  55. components: {
  56. BuTable,
  57. BuPagination
  58. },
  59. props: {
  60. url: {
  61. type: String,
  62. default: ''
  63. },
  64. filter: {
  65. type: Object,
  66. default: () => {
  67. return {}
  68. }
  69. },
  70. /**
  71. * 表格数据
  72. */
  73. data: {
  74. type: Array,
  75. default: () => []
  76. },
  77. /**
  78. * 表格配置
  79. */
  80. setting: {
  81. type: Array,
  82. default: () => []
  83. },
  84. height: {
  85. type: String | Number
  86. },
  87. excel: {
  88. type: Object | String,
  89. default: ''
  90. },
  91. total: {
  92. type: Number,
  93. default: 0
  94. },
  95. page: {
  96. type: Number,
  97. default: 1
  98. },
  99. limit: {
  100. type: Number,
  101. default: 20
  102. },
  103. pageSizes: {
  104. type: Array,
  105. default () {
  106. return [10, 20, 30, 50]
  107. }
  108. },
  109. layout: {
  110. type: String,
  111. default: 'total, prev, pager, next, jumper'
  112. },
  113. background: {
  114. type: Boolean,
  115. default: true
  116. },
  117. autoScroll: {
  118. type: Boolean,
  119. default: true
  120. },
  121. hiddenPagination: {
  122. type: Boolean,
  123. default: false
  124. }
  125. },
  126. data () {
  127. return {
  128. tableData: [],
  129. p_limit: 20,
  130. p_page: 1,
  131. p_total: 0
  132. }
  133. },
  134. watch: {
  135. data: {
  136. handler (val) {
  137. this.tableData = val
  138. },
  139. immediate: true
  140. },
  141. url: {
  142. handler (val) {
  143. if (val) {
  144. this.fetchData()
  145. }
  146. },
  147. immediate: true
  148. },
  149. limit: {
  150. handler (val) {
  151. this.p_limit = val
  152. },
  153. immediate: true
  154. },
  155. page: {
  156. handler (val) {
  157. this.p_page = val
  158. },
  159. immediate: true
  160. }
  161. },
  162. methods: {
  163. fetchData (filterData, type) {
  164. if (type === 'init') {
  165. this.p_page = 1
  166. }
  167. let data = { 'PN': this.p_page, 'PAGE_SIZE': this.p_limit }
  168. if (filterData) {
  169. data = Object.assign({}, data, filterData)
  170. } else {
  171. data = Object.assign({}, data, this.filter)
  172. }
  173. request({
  174. url: this.url,
  175. data
  176. }).then(res => {
  177. this.tableData = res.data
  178. res.extraData && res.extraData.page && (this.p_total = res.extraData.page.querySize)
  179. })
  180. },
  181. handleOperation (index, val, item) {
  182. this.$emit('operation', index, val, item)
  183. },
  184. handleCommand (command, template, item) {
  185. this.$emit('command', command, template, item)
  186. },
  187. exportExcel (excel) {
  188. this.$emit("beforeExport")
  189. if (excel.url) {
  190. this.excel = excel
  191. }
  192. const { url, body = {} } = this.excel
  193. if (!url) {
  194. return
  195. }
  196. this.$axios({ method: 'POST', type: 'form', url, body }, res => {
  197. let url = res.extraData['URL']
  198. if (url) {
  199. window.open(url)
  200. } else if (res.code === 400 || !url) {
  201. this.$message({
  202. message: '导出失败',
  203. type: 'warn'
  204. })
  205. }
  206. })
  207. },
  208. handlePagination ({ page, limit }) {
  209. this.p_page = page
  210. this.fetchData()
  211. this.$emit('pagination', { page, limit })
  212. },
  213. }
  214. }
  215. </script>
  216. <style scoped lang="scss">
  217. .fl-right {
  218. float: right;
  219. }
  220. .bu-container {
  221. padding: 30px 15px;
  222. }
  223. </style>