diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..e812394 --- /dev/null +++ b/.babelrc @@ -0,0 +1,6 @@ +{ + "presets": [ + ["env", { "modules": false }], + "stage-3" + ] +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e4a3e2d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,26 @@ +root = true + +[*] +indent_style = tab +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false + +[*.js] +indent_size = 4 + +[*.ts] +indent_size = 4 + +[*.vue] +indent_size = 4 + +[*.css] +indent_size = 4 + +[Makefile] +indent_style = tab diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1511548 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +.DS_Store +node_modules/ +npm-debug.log +yarn-error.log + +# Editor directories and files +.idea +*.suo +*.ntvs* +*.njsproj +*.sln diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f014957 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 CasualMing + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b9db49b --- /dev/null +++ b/README.md @@ -0,0 +1,91 @@ + +## 功能 + + 列表表格及分页 基于element-ui 组件进行二次封装 + +## 说明 +> 由于表格配置项比较多,所以具体的每一项配置,可以查看 doc 目录具体的 md 文档 + +## 功能 + 1. 表格支持 json 配置 + 2. 支持加载 loading + 3. 支持表格分页 + 4. 支持表格自定义组件渲染 + 5. 表格支持单选、多选处理,内置单选、多选回显逻辑处理 + 6. 表格支持滚动加载分页 + 7. 组件字段无缝衔接 `element-ui` 的 `table` 组件字段配置 + 8. 高度支持自定义,定制化 + + +## 配置 + +| 属性 | 描述 | 类型 | 详情 | +|-----------------|-------|------------------|------| +| data | 表格所需要渲染的数据源 | JSONArray | -- | +| columns | 表头数据及配置 | JSONArray | [columns 配置](doc/columns.md) | +| pagination | 表格分页数据配置 | Object | [pagination 配置](doc/pagination.md) | +| options | 表格配置 | JSONArray | [options 配置](doc/options.md) | +| row-handle | 表格操作列配置 | JSONArray | [row-handle 配置](doc/row-handle.md) | +| loading-options | 表格 loading 配置 | Object | [loading-options 配置](doc/loading-options.md) | +| index-row | 表格单选配置 | Object | [index-row 配置](doc/index-row.md) | +| selection-row | 表格多选配置 | Object | [selection-row 配置](doc/selection-row.md) | +| load-more-options | 表格滚动加载更多配置 | Object | [load-more-options 配置](doc/load-more-options.md) | + + +## 事件 + +| 属性 | 描述 | 类型 | 详情 | +|-----------------|-------|------------------|------| +| events | 表格可监听的事件 | Function | [events 配置](doc/events.md) | + + +## 事件 methods + +| 属性 | 描述 | 类型 | 详情 | +|-----------------|-------|------------------|------| +| methods | 表格调用的方法 | Function | [methods 配置](doc/methods.md) | + +## 插槽 Slot + +| 属性 | 描述 | 类型 | 详情 | +|-----------------|-------|------------------|------| +| slots | 表格可使用的插槽 | String | [slots 配置](doc/slots.md) | + + +## 安装 + +使用npm +``` bash +npm i element-ui tabulation -S +``` + +使用yarn +``` bash +yarn add element-ui tabulation +``` + +## 在项目中使用 + +在`main.js`中写入以下内容: + +``` js +import Vue from 'vue' +import ElementUI from 'element-ui' +import 'element-ui/lib/theme-chalk/index.css' +import Tabulation from 'tabulation' + +Vue.use(ElementUI) +Vue.use(Tabulation) + +new Vue({ + el: '#app', + render: h => h(App) +}) +``` + +之后就可以在项目中使用 `Tabulation` 了。 \ No newline at end of file diff --git a/doc/columns.md b/doc/columns.md new file mode 100644 index 0000000..ebe3cd7 --- /dev/null +++ b/doc/columns.md @@ -0,0 +1,222 @@ + +# 配置: columns + +## title + +* 说明: 表格列名 +* 类型: String +* 可选值: 无 +* 默认值: 无 + +## key + +* 说明: data 中对应列的键名(表格内需要通过 `component` 进行自定义渲染 `element-ui` 的组件的时候,用于进行双向数据绑定,改变的值来源 `data`) +* 类型: String +* 可选值: 无 +* 默认值: 无 + +## width + +* 说明: 对应列的宽度 +* 类型: String +* 可选值: 无 +* 默认值: 无 + +## minWidth + +* 说明: 对应列的最小宽度,与 width 的区别是 width 是固定的,minWidth 会把剩余宽度按比例分配给设置了 minWidth 的列 +* 类型: String +* 可选值: 无 +* 默认值: 无 + +## fixed + +* 说明: 列是否固定在左侧或者右侧,true 表示固定在左侧 +* 类型: String / Boolean +* 可选值: true / left / right +* 默认值: 无 + +## renderHeader + +* 说明: 列标题 Label 区域渲染使用的 Function +* 类型: Function (h, { column, $index }) +* 可选值: 无 +* 默认值: 无 + +## sortable + +* 说明: 对应列是否可以排序,如果设置为 'custom',则代表用户希望远程排序,需要监听 Table 的 sort-change 事件 +* 类型: Boolean / String +* 可选值: true, false, 'custom' +* 默认值: false + +## sortMethod + +* 说明: 对数据进行排序的时候使用的方法,仅当 sortable 设置为 true 的时候有效,需返回一个数字,和 Array.sort 表现一致 +* 类型: Function (a, b) +* 可选值: 无 +* 默认值: 无 + +## sortBy + +* 说明: 指定数据按照哪个属性进行排序,仅当 sortable 设置为 true 且没有设置 sort-method 的时候有效。如果 sort-by 为数组,则先按照第 1 个属性排序,如果第 1 个相等,再按照第 2 个排序,以此类推 +* 类型: String / Array / Function (row, index) +* 可选值: 无 +* 默认值: 无 + +## sortOrders + +* 说明: 数据在排序时所使用排序策略的轮转顺序,仅当 sortable 为 true 时有效。需传入一个数组,随着用户点击表头,该列依次按照数组中元素的顺序进行排序 +* 类型: array +* 可选值: 数组中的元素需为以下三者之一:ascending 表示升序,descending 表示降序,null 表示还原为原始顺序 +* 默认值: ['ascending', 'descending', null] + +## resizable + +* 说明: 对应列是否可以通过拖动改变宽度(需要在 `options` 中设置 border 属性为 true +* 类型: Boolean +* 可选值: 无 +* 默认值: true + +## formatter + +* 说明: 用来格式化内容 +* 类型: Function (row, column, cellValue, index) +* 可选值: 无 +* 默认值: 无 + +## showOverflowTooltip + +* 说明: 当内容过长被隐藏时显示 tooltip +* 类型: Boolean +* 可选值: 无 +* 默认值: false + +## align + +* 说明: 对齐方式 +* 类型: String +* 可选值: left / center / right +* 默认值: left + +## headerAlign + +* 说明: 表头对齐方式,若不设置该项,则使用表格的对齐方式 +* 类型: String +* 可选值: left / center / right +* 默认值: 无 + +## className + +* 说明: 列的 className +* 类型: String +* 可选值: 无 +* 默认值: 无 + +## labelClassName + +* 说明: 当前列标题的自定义类名 +* 类型: String +* 可选值: 无 +* 默认值: 无 + +## filters + +* 说明: 数据过滤的选项,数组格式,数组中的元素需要有 text 和 value 属性。 +* 类型: Function ({ text, value }) +* 可选值: 无 +* 默认值: 无 + +## filterPlacement + +* 说明: 过滤弹出框的定位 +* 类型: String +* 可选值: top / top-start / top-end / bottom / bottom-start / bottom-end / left / left-start / left-end / right / right-start / right-end +* 默认值: bottom + +## filterMultiple + +* 说明: 数据过滤的选项是否多选 +* 类型: Boolean +* 可选值: 无 +* 默认值: true + +## filterMethod + +* 说明: 数据过滤使用的方法,如果是多选的筛选项,对每一条数据会执行多次,任意一次返回 true 就会显示。 +* 类型: Function (value, row, column) +* 可选值: 无 +* 默认值: 无 + +## filteredValue + +* 说明: 选中的数据过滤项,如果需要自定义表头过滤的渲染方式,可能会需要此属性。 +* 类型: Array +* 可选值: 无 +* 默认值: 无 +## show + +* 说明: 控制是否显示自定义按钮,类型为 `Function` 时,返回值只能为 `true` 或 `false` +* 类型: Boolean / Function (index, row) +* 可选值: 无 +* 默认值: true +## children + +* 说明: 表格需要多级渲染的时候,可传入此参数,最高接收三层(数据同 `columns` ) +* 类型: JSONArray +* 可选值: 无 +* 默认值: 无 + +## component + +* 说明: 表格渲染的组件,组件请参考 [组件](http://element-cn.eleme.io/#/zh-CN/component) +* 类型: Object +* 可选属性: name / show / buttonMode / options / props / render / 其他需要绑定到组件上的属性 +* 默认值: 无 + ### name + + * 说明: 表格渲染的组件名,组件请参考 [组件](http://element-cn.eleme.io/#/zh-CN/component) + * 类型: String + * 可选值: el-input / el-input-number / el-radio / el-checkbox / el-select / el-cascader / el-switch / el-slider / el-time-select / el-time-picker / el-date-picker / el-rate / el-color-picker / 自定义组件 + * 默认值: 无 + + ### show + + * 说明: 表格渲染的组件是否展示 + * 类型: Boolean / Function + * 可选值: 无 + * 默认值: true + + ### buttonMode + + * 说明: 当渲染的组件是 `checkbox` 类型的时候,用于控制是渲染 `el-checkbox-button` 还是 `el-checkbox` + * 类型: Boolean + * 可选值: true / false + * 默认值: false + + ### options + + * 说明: 当渲染的组件是 `el-radio、el-radio-button、el-checkbox-button、el-checkbox、el-select(el-option)` 这几种可以通过数组循环创建,可用于循环创建,此时通过循环配置的响应数据,经过传入的 `data` 通过 `columns` 中的 `key` 取到对应的值。 + * 类型: JSONArray + * 可选值: 无 + * 默认值: 无 + + ### props + + * 说明: 表格内进行自定义组件渲染的时候,传入自定义组件的参数 + * 类型: Object + * 可选值: 无 + * 默认值: 无 + + ### render + + * 说明: 表格内不想单独定义组件文件,但是现有的表格布局不满足需求,可使用 `render` 函数,具体使用方法可见[render](https://cn.vuejs.org/v2/api/#render) + * 类型: Object + * 可选值: 无 + * 默认值: 无 diff --git a/doc/events.md b/doc/events.md new file mode 100644 index 0000000..14e3406 --- /dev/null +++ b/doc/events.md @@ -0,0 +1,119 @@ + + +# 事件 + +## select + +* 说明: (多选模式下)当用户手动勾选数据行的 Checkbox 时触发的事件 +* 回调参数: selection, row, selectionList + +## select-all + +* 说明: (多选模式下)当用户手动勾选全选 Checkbox 时触发的事件 +* 回调参数: selection, selectionList + +## selection-change + +* 说明: (多选模式下)当选择项发生变化时会触发该事件 +* 回调参数: selection,selectionList + +## selection-index + +* 说明: (单选模式下)当选择项发生变化时会触发该事件 +* 回调参数: selection , index, row +## current-change + +* 说明: 当表格的当前行发生变化的时候会触发该事件,如果要高亮当前行,请打开 options 中的 highlight-current-row 属性 +* 回调参数: currentRow, oldCurrentRow + +## cell-mouse-enter + +* 说明: 当单元格 hover 进入时会触发该事件 +* 回调参数: row, column, cell, event + +## cell-mouse-leave + +* 说明: 当单元格 hover 退出时会触发该事件 +* 回调参数: row, column, cell, event + +## cell-click + +* 说明: 当某个单元格被点击时会触发该事件 +* 回调参数: row, column, cell, event + +## cell-dblclick + +* 说明: 当某个单元格被双击击时会触发该事件 +* 回调参数: row, column, cell, event + +## row-click + +* 说明: 当某一行被点击时会触发该事件 +* 回调参数: row, event, column + +## row-contextmenu + +* 说明: 当某一行被鼠标右键点击时会触发该事件 +* 回调参数: row, event + +## row-dblclick + +* 说明: 当某一行被双击时会触发该事件 +* 回调参数: row, event + +## header-click + +* 说明: 当某一列的表头被点击时会触发该事件 +* 回调参数: column, event + +## header-contextmenu + +* 说明: 当某一列的表头被鼠标右键点击时触发该事件 +* 回调参数: column, event + +## sort-change + +* 说明: 当表格的排序条件发生变化的时候会触发该事件 +* 回调参数: { column, prop, order, ref } + +## filter-change + +* 说明: 当表格的筛选条件发生变化的时候会触发该事件,参数的值是一个对象,对象的 key 是 column 的 columnKey,对应的 value 为用户选择的筛选条件的数组。 +* 回调参数: filters + + +## pagination-size-change + +* 说明: 当分页 pageSize 改变时会触发 +* 回调参数: pageSize + +## pagination-current-change + +* 说明: 当分页 currentPage 改变时会触发 +* 回调参数: currentPage + +## pagination-prev-click + +* 说明: 当分页上一页按钮被用户点击改变当前页后触发 +* 回调参数: currentPage + +## pagination-next-click + +* 说明: 当分页下一页按钮被用户点击改变当前页后触发 +* 回调参数: currentPage + +## cell-data-change + +* 说明: 表格内编辑时单元格数据改变触发 +* 回调参数: rowIndex,key,value,row + +## load-more + +* 说明: 表格支持滚动加载的时候,滚动到底部时触发事件 +* 回调参数: 无 \ No newline at end of file diff --git a/doc/index-row.md b/doc/index-row.md new file mode 100644 index 0000000..c602d65 --- /dev/null +++ b/doc/index-row.md @@ -0,0 +1,100 @@ + +# 配置: index-row + +## title + +* 说明: indexRow 列名 +* 类型: String +* 可选值: 无 +* 默认值: 无 + +## width + +* 说明: indexRow 宽度 +* 类型: String +* 可选值: 无 +* 默认值: 无 + +## minWidth + +* 说明: indexRow 最小宽度,与 width 的区别是 width 是固定的,minWidth 会把剩余宽度按比例分配给设置了 minWidth 的列 +* 类型: String +* 可选值: 无 +* 默认值: 无 + +## fixed + +* 说明: indexRow 是否固定在左侧或者右侧,true 表示固定在左侧 +* 类型: String / Boolean +* 可选值: true / left / right +* 默认值: 无 + +## renderHeader + +* 说明: indexRow 标题 Label 区域渲染使用的 Function +* 类型: Function (h, { column, $index }) +* 可选值: 无 +* 默认值: 无 + +## resizable + +* 说明: indexRow 是否可以通过拖动改变宽度(需要在 options 中设置 border 属性为 true +* 类型: Boolean +* 可选值: 无 +* 默认值: true + +## align + +* 说明: 对齐方式 +* 类型: String +* 可选值: left / center / right +* 默认值: left + +## headerAlign + +* 说明: 表头对齐方式,若不设置该项,则使用表格的对齐方式 +* 类型: String +* 可选值: left / center / right +* 默认值: 无 + +## className + +* 说明: indexRow 的 className +* 类型: String +* 可选值: 无 +* 默认值: 无 + +## labelClassName + +* 说明: indexRow 标题的自定义类名 +* 类型: String +* 可选值: 无 +* 默认值: 无 + +## allListData + +* 说明: indexRow 表格已加载的所有列表的数据(单选模式下,必传) +* 是否必须: 是 +* 类型: Array +* 可选值: 无 +* 默认值: 无 + +## selection + +* 说明: 外部传入被选中的数据(需要数据的唯一值) +* 类型: String / Number +* 可选值: 无 +* 默认值: 无 + +## idKey + +* 说明: 每条数据的唯一标识对应的字段名(用于单选筛选) +* 类型: String +* 可选值: 无 +* 默认值: id \ No newline at end of file diff --git a/doc/load-more-options.md b/doc/load-more-options.md new file mode 100644 index 0000000..888c55d --- /dev/null +++ b/doc/load-more-options.md @@ -0,0 +1,45 @@ + +# load-more-options + +## is-scroll + +* 说明: 是否监听表格滚动到底部 +* 类型: Boolean +* 可选值: 无 +* 默认值: false + +## load-class + +* 说明: 加载更多 提示元素的 class +* 类型: String + +## load-show + +* 说明: 滚动加载更多的时候,是否展示加载提示 +* 类型: Boolean +* 默认: false + +## load-show-icon + +* 说明: 加载更多时的 icon +* 可选值: element-ui的 icon 或者 base64的图片 +* 类型: String +* 默认: el-icon-loading + +## load-show-text + +* 说明: 滚动加载更多的时候,展示的文字 +* 类型: Boolean +* 默认: '正在加载中' + +## load-no-more-text + +* 说明: 加载更多完成后没有更多时的文字展示 +* 类型: String +* 默认: '没有更多了……' diff --git a/doc/loading-options.md b/doc/loading-options.md new file mode 100644 index 0000000..6e98b1b --- /dev/null +++ b/doc/loading-options.md @@ -0,0 +1,29 @@ +# 配置: loading-options + +## loading + +* 说明: 是否展示加载状态 +* 类型: Boolean +* 可选值: false / true +* 默认值: false + +## text + +* 说明: 加载状态文案 +* 类型: String +* 可选值: 无 +* 默认值: '拼命加载中' + +## spinner + +* 说明: 加载状态图标类名 +* 类型: String +* 可选值: 无 +* 默认值: 'el-icon-loading' + +## background + +* 说明: 加载状态背景色值 +* 类型: String +* 可选值: 无 +* 默认值: 'rgba(255, 255, 255, 0.5)' diff --git a/doc/methods.md b/doc/methods.md new file mode 100644 index 0000000..df50475 --- /dev/null +++ b/doc/methods.md @@ -0,0 +1,18 @@ + +# methods + +## tableScrollTop + +* 说明: 表格内局部滚动的时候,滚动到顶部 +* 参数: 无 + +## scrollToTop + +* 说明: 指定的元素滚动到距离顶部多少 `px` 停止(适用于任何可滚动的元素,可控制滚动快慢) +* 参数: taget(滚动的元素 Element 类型), distance = 50 (每次滚动多少 `px` ), top(距离顶部多少 `px` 停止) \ No newline at end of file diff --git a/doc/options.md b/doc/options.md new file mode 100644 index 0000000..4890189 --- /dev/null +++ b/doc/options.md @@ -0,0 +1,246 @@ + +# 配置: options + +## height + +* 说明: 表格的高度,默认为自动高度。如果 height 为 number 类型,单位 px;如果 height 为 String 类型,则这个高度会设置为表格的 style.height 的值,表格的高度会受控于外部样式。 +* 类型: String / Number +* 可选值: 无 +* 默认值: 无 + +## maxHeight + +* 说明: 表格的最大高度 +* 类型: String / Number +* 可选值: 无 +* 默认值: 无 + +## stripe + +* 说明: 是否为斑马纹模式 +* 类型: Boolean +* 可选值: 无 +* 默认值: true + +## border + +* 说明: 是否带有纵向边框 +* 类型: Boolean +* 可选值: 无 +* 默认值: false + +## size + +* 说明: 表格的尺寸 +* 类型: String +* 可选值: medium / small / mini +* 默认值: 无 + +## fit + +* 说明: 列的宽度是否自撑开 +* 类型: Boolean +* 可选值: 无 +* 默认值: true + +## showHeader + +* 说明: 是否显示表头 +* 类型: Boolean +* 可选值: 无 +* 默认值: true + +## highlightCurrentRow + +* 说明: 是否要高亮当前行 +* 类型: Boolean +* 可选值: 无 +* 默认值: false + +## currentRowKey + +* 说明: 当前行的 key,只写属性 +* 类型: String / Number +* 可选值: 无 +* 默认值: 无 + +## rowClassName + +* 说明: 行的 className 的回调方法,也可以使用字符串为所有行设置一个固定的 className。 +* 类型: Function ({row, rowIndex}) / String +* 可选值: 无 +* 默认值: 无 + +## rowStyle + +* 说明: 行的 style 的回调方法,也可以使用一个固定的 Object 为所有行设置一样的 Style。 +* 类型: Function ({row, rowIndex}) / Object +* 可选值: 无 +* 默认值: 无 + +## cellClassName + +* 说明: 单元格的 className 的回调方法,也可以使用字符串为所有单元格设置一个固定的 className。 +* 类型: Function ({row, column, rowIndex, columnIndex}) / String +* 可选值: 无 +* 默认值: 无 + +## cellStyle + +* 说明: 单元格的 style 的回调方法,也可以使用一个固定的 Object 为所有单元格设置一样的 Style。 +* 类型: Function ({row, column, rowIndex, columnIndex}) / Object +* 可选值: 无 +* 默认值: 无 + +## headerRowClassName + +* 说明: 表头行的 className 的回调方法,也可以使用字符串为所有表头行设置一个固定的 className。 +* 类型: Function ({row, rowIndex}) / String +* 可选值: 无 +* 默认值: 无 + +## headerRowStyle + +* 说明: 表头行的 style 的回调方法,也可以使用一个固定的 Object 为所有表头行设置一样的 Style。 +* 类型: Function ({row, rowIndex}) / Object +* 可选值: 无 +* 默认值: 无 + +## headerCellClassName + +* 说明: 表头单元格的 className 的回调方法,也可以使用字符串为所有表头单元格设置一个固定的 className。 +* 类型: Function ({row, column, rowIndex, columnIndex}) / String +* 可选值: 无 +* 默认值: 无 + +## headerCellStyle + +* 说明: 表头单元格的 style 的回调方法,也可以使用一个固定的 Object 为所有表头单元格设置一样的 Style。 +* 类型: Function ({row, column, rowIndex, columnIndex}) / Object +* 可选值: 无 +* 默认值: 无 + +## rowKey + +* 说明: 行数据的 Key,用来优化表格的渲染;在使用 reserveSelection 功能的情况下,该属性是必填的。类型为 String 时,支持多层访问:user.info.id,但不支持 user.info[0].id,此种情况请使用 function。 +* 类型: Function (row) / String +* 可选值: 无 +* 默认值: 无 + +## emptyText + +* 说明: 空数据时显示的文本内容 +* 类型: String +* 可选值: 无 +* 默认值: 无 + +## defaultSort + +* 说明: 默认的排序列的 prop 和顺序。它的prop属性指定默认的排序的列,order指定默认排序的顺序 +* 类型: Object +* 可选值: order: ascending / descending +* 默认值: 如果只指定了 prop, 没有指定order, 则默认顺序是 ascending + +## tooltipEffect + +* 说明: tooltip effect 属性 +* 类型: String +* 可选值: dark / light +* 默认值: 无 + +## showSummary + +* 说明: 是否在表尾显示合计行 +* 类型: Boolean +* 可选值: 无 +* 默认值: false + +## sumText + +* 说明: 合计行第一列的文本 +* 类型: String +* 可选值: 无 +* 默认值: 合计 + +## summaryMethod + +* 说明: 自定义的合计计算方法 +* 类型: Function ({ columns, data }) +* 可选值: 无 +* 默认值: 无 + +## spanMethod + +* 说明: 合并行或列的计算方法 +* 类型: Function ({ row, column, rowIndex, columnIndex }) +* 可选值: 无 +* 默认值: 无 + +## selectOnIndeterminate + +* 说明: 在多选表格中,当仅有部分行被选中时,点击表头的多选框时的行为。若为 true,则选中所有行;若为 false,则取消选择所有行 +* 类型: Boolean +* 可选值: 无 +* 默认值: true + +## bodyScroll + +* 说明: table 表格是否允许表格内容区域进行局部滚动(局部滚动的时候,需要设置 table-body 的最大高度) +* 类型: Boolean +* 可选值: 无 +* 默认值: false + +## isTipsImg + +* 说明: `table` 表格没有数据时是否展示缺省图片 +* 类型: Boolean +* 可选值: 无 +* 默认值: true + +## isTipsText + +* 说明: `table` 表格没有数据时是否展示文案提示 +* 类型: Boolean +* 可选值: 无 +* 默认值: true + +## tipsTop + +* 说明: `table` 表格没有数据时缺省提示距离顶部的距离 +* 类型: String +* 可选值: 无 +* 默认值: 20px + +## tipsText + +* 说明: `table` 表格没有数据时缺省提示的文案 +* 类型: String +* 可选值: 无 +* 默认值: 暂无数据 + +## loadingHeight + +* 说明: `table` 表格没有数据时缺省提示的容器高度 +* 类型: String +* 可选值: 无 +* 默认值: 100% + +## scrollToTop + +* 说明: `table` 表格数据改变时,要不要进行自动滚动到页面顶部 +* 类型: Boolean +* 可选值: true / false +* 默认值: true + +## scrollToTopMargin + +* 说明: `table` 表格数据改变时,滚动到距离顶部多少px +* 类型: Number +* 可选值: 无 +* 默认值: 0 \ No newline at end of file diff --git a/doc/pagination.md b/doc/pagination.md new file mode 100644 index 0000000..977ff91 --- /dev/null +++ b/doc/pagination.md @@ -0,0 +1,85 @@ + +# 配置: pagination + +## currentPage + +* 说明: 表格当前页 +* 类型: Number +* 可选值: 无 +* 默认值: 无 + +## pageSize + +* 说明: 表格每页行数 +* 类型: Number +* 可选值: 无 +* 默认值: 10 + +## pageCount + +* 说明: 表格的页码总数 +* 类型: Number +* 可选值: 无 +* 默认值: 无 + +## small + +* 说明: 是否使用小型分页样式 +* 类型: Boolean +* 可选值: 无 +* 默认值: false + +## background + +* 说明: 是否为分页按钮添加背景色 +* 类型: Boolean +* 可选值: 无 +* 默认值: true + +## layout + +* 说明: 组件布局,子组件名用逗号分隔 +* 类型: String +* 可选值: `sizes`, `prev`, `pager`, `next`, `jumper`, `->`, `total`, `slot` +* 默认值: 'total, sizes, prev, pager, next, jumper' + +## pageSizes + +* 说明: 每页显示个数选择器的选项设置 +* 类型: Number[] +* 可选值: 无 +* 默认值: [10, 20, 30, 40, 50, 100] + +## popperClass + +* 说明: 每页显示个数选择器的下拉框类名 +* 类型: String +* 可选值: 无 +* 默认值: 无 + +## prevText + +* 说明: 替代图标显示的上一页文字 +* 类型: String +* 可选值: 无 +* 默认值: 无 + +## nextText + +* 说明: 替代图标显示的上一页文字 +* 类型: String +* 可选值: 无 +* 默认值: 无 + +## disabled + +* 说明: 是否禁用 +* 类型: Boolean +* 可选值: 无 +* 默认值: false diff --git a/doc/row-handle.md b/doc/row-handle.md new file mode 100644 index 0000000..ddd7c30 --- /dev/null +++ b/doc/row-handle.md @@ -0,0 +1,149 @@ + +# 配置: row-handle +## columnHeader + +* 说明: 操作列表头文字 +* 类型: String +* 可选值: 无 +* 默认值: 操作 + +## width + +* 说明: 操作列宽度 +* 类型: String +* 可选值: 无 +* 默认值: 无 + +## minWidth + +* 说明: 操作列最小宽度 +* 类型: String +* 可选值: 无 +* 默认值: 无 + +## fixed + +* 说明: 操作列固定列 +* 类型: String / Boolean +* 可选值: true / left / right +* 默认值: 无 + +## align + +* 说明: 操作列对齐方式 +* 类型: String +* 可选值: left / center / right +* 默认值: left + +## renderHeader + +* 说明: 操作列 Label 区域渲染使用的 Function +* 类型: Function (h, { column, $index }) +* 可选值: 无 +* 默认值: 无 + +## custom + +* 说明: 自定义按钮 +* 类型: Array +* 可选值: 无 +* 默认值: 无 + + ---- + ### name + + * 说明: 操作列的组件名称 + * 类型: String + * 可选值: el-button / el-switch + * 默认值: 无 + + ### emit + + * 说明: 自定义按钮监听的事件 + * 类型: String + * 可选值: 无 + * 默认值: 无 + + ### text + + * 说明: 自定义按钮文字 + * 类型: String + * 可选值: 无 + * 默认值: 无 + + ### size + + * 说明: 自定义按钮尺寸 + * 类型: String + * 可选值: medium / small / mini + * 默认值: 无 + + ### type + + * 说明: 自定义按钮类型 + * 类型: String + * 可选值: primary / success / warning / danger / info / text + * 默认值: 无 + + ### icon + + * 说明: 自定义按钮图标类名 + * 类型: String + * 可选值: 无 + * 默认值: 无 + + ### show + + * 说明: 控制是否显示自定义按钮,类型为 `Function` 时,返回值只能为 `true` 或 `false` + * 类型: Boolean / Function (index, row) + * 可选值: 无 + * 默认值: true + + ### disabled + + * 说明: 控制是否禁用自定义按钮,类型为 `Function` 时,返回值只能为 `true` 或 `false` + * 类型: Boolean / Function (index, row) + * 可选值: 无 + * 默认值: false + + ### component + + * 说明: 操作列的自定义组件 + * 类型: Object + * 可选值: 无 + * 默认值: 无 + + --- + #### name + + * 说明: 自定义组件的组件名称 + * 类型: String + * 可选值: 无 + * 默认值: 无 + + #### key + + * 说明: 自定义组件的组件在 data 中绑定值的属性名 + * 类型: String + * 可选值: 无 + * 默认值: 无 + + #### props + + * 说明: 自定义组件传参对象 + * 类型: Object + * 可选值: 无 + * 默认值: 无 + + #### render + + * 说明: 自定义渲染组件方法 + * 类型: Function(h, scope.row, scope.column, scope.$index) + * 可选值: 无 + * 默认值: 无 \ No newline at end of file diff --git a/doc/selection-row.md b/doc/selection-row.md new file mode 100644 index 0000000..e16db12 --- /dev/null +++ b/doc/selection-row.md @@ -0,0 +1,107 @@ +# 配置: selection-row + +## title + +* 说明: selectionRow列名 +* 类型: String +* 可选值: 无 +* 默认值: 无 + +## width + +* 说明: selectionRow宽度 +* 类型: String +* 可选值: 无 +* 默认值: 无 + +## minWidth + +* 说明: selectionRow 最小宽度,与 width 的区别是 width 是固定的,minWidth 会把剩余宽度按比例分配给设置了 minWidth 的列 +* 类型: String +* 可选值: 无 +* 默认值: 无 + +## fixed + +* 说明: selectionRow 是否固定在左侧或者右侧,true 表示固定在左侧 +* 类型: String / Boolean +* 可选值: true / left / right +* 默认值: 无 + +## renderHeader + +* 说明: selectionRow 标题 Label 区域渲染使用的 Function +* 类型: Function (h, { column, $index }) +* 可选值: 无 +* 默认值: 无 + +## resizable + +* 说明: selectionRow 是否可以通过拖动改变宽度(需要在 options 中设置 border 属性为 true +* 类型: Boolean +* 可选值: 无 +* 默认值: true + +## align + +* 说明: 对齐方式 +* 类型: String +* 可选值: left / center / right +* 默认值: left + +## headerAlign + +* 说明: 表头对齐方式,若不设置该项,则使用表格的对齐方式 +* 类型: String +* 可选值: left / center / right +* 默认值: 无 + +## className + +* 说明: selectionRow 的 className +* 类型: String +* 可选值: 无 +* 默认值: 无 + +## labelClassName + +* 说明: selectionRow 标题的自定义类名 +* 类型: String +* 可选值: 无 +* 默认值: 无 +## allListData + +* 说明: selectionRow 表格已加载的所有列表的数据(单选模式下,必传) +* 是否必须: 是 +* 类型: Array +* 可选值: 无 +* 默认值: 无 +## selectionList + +* 说明: 外部传入被选中的数据 +* 类型: Array +* 可选值: 无 +* 默认值: 无 + +## idKey + +* 说明: 每条数据的唯一标识对应的字段名(用于多选筛选) +* 类型: String +* 可选值: 无 +* 默认值: id + +## isAllActived + +* 说明: 表格表头多选按钮是否全选 +* 类型: Boolean +* 可选值: true / false +* 默认值: false +* 必传 (需要外部传入初始值) + +## isSemi + +* 说明: 表格表头多选按钮是否半选 +* 类型: Boolean +* 可选值: true / false +* 默认值: false +* 必传 (需要外部传入初始值) \ No newline at end of file diff --git a/doc/slots.md b/doc/slots.md new file mode 100644 index 0000000..dde82db --- /dev/null +++ b/doc/slots.md @@ -0,0 +1,38 @@ + +# 插槽 + +## empty + +* 说明: 表格无数据时的插槽 +* 默认: 无 + +## append + +* 说明: 插入至表格最后一行之后的内容,如果需要对表格的内容进行无限滚动操作,可能需要用到这个 slot。若表格有合计行,该 slot 会位于合计行之上。 +* 默认: 暂可通过 `loadMoreOptions` 配置滚动加载时的列表样式 + +## pageSlot + +* 说明: 表格分页 slot的插槽 +* 默认: 无 + +## index + +* 说明: 单选状态下的插槽 +* 默认: radio + +## selectionHeader + +* 说明: 多选状态下的标头插槽 +* 默认: checkbox + +## selection + +* 说明: 多选状态下的每个单元格插槽 +* 默认: checkbox \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..a2958b7 --- /dev/null +++ b/index.js @@ -0,0 +1 @@ +module.exports = require('./src') diff --git a/package.json b/package.json new file mode 100644 index 0000000..06626f9 --- /dev/null +++ b/package.json @@ -0,0 +1,40 @@ +{ + "name": "tabulation", + "version": "0.0.1", + "description": "列表表格及分页 基于element-ui 组件进行二次封装", + "main": "index.js", + "scripts": { + "build": "cross-env NODE_ENV=production webpack --progress --hide-modules" + }, + "keywords": [ + "table", + "vue", + "element" + ], + "author": "CasualMing", + "license": "ISC", + "dependencies": { + "lodash.get": "^4.4.2", + "lodash.set": "^4.3.2" + }, + "browserslist": [ + "> 1%", + "last 2 versions", + "not ie <= 8" + ], + "devDependencies": { + "babel-core": "^6.26.0", + "babel-loader": "^7.1.2", + "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", + "node-sass": "^4.5.3", + "sass-loader": "^6.0.6", + "vue-loader": "^13.0.5", + "vue-template-compiler": "^2.5.19", + "webpack": "^3.6.0", + "webpack-dev-server": "^2.9.1" + } +} \ No newline at end of file diff --git a/src/Tabulation.less b/src/Tabulation.less new file mode 100644 index 0000000..52a63b0 --- /dev/null +++ b/src/Tabulation.less @@ -0,0 +1,158 @@ +@import url('../../assets/style/variables'); + +.tabulation { + &-more { + height: 50px; + + &-load { + background-color: @color-bg; + height: 50px; + line-height: 50px; + text-align: center; + font-size: 14px; + color: #999; + + &-img { + width: 20px; + height: 20px; + } + } + } + + &-no-more { + background-color: @color-bg; + height: 50px; + line-height: 50px; + text-align: center; + font-size: 14px; + color: #999; + } + + .el-table { + border: 1px solid #E9EDF5; + border-radius: 8px; + overflow: hidden; + + .el-table__header { + th { + &.el-table__cell { + background: #F3F6FD; + } + + /* 表格表头左侧样式 */ + &:first-child { + .cell { + padding-left: 28px; + } + } + } + } + + .el-table__body { + .el-table__row--striped { + td { + &.el-table__cell { + background-color: @table-striped_background; + } + } + } + + + tr { + + &.hover-row, + & { + &.el-table__row { + &:hover { + td { + &.el-table__cell { + background-color: @table-hover-background; + } + } + } + } + } + } + + /* 表格内容左侧样式 */ + .el-table__row { + td { + &:first-child { + .cell { + padding-left: 28px; + } + } + } + } + } + + /* 表格多选框 */ + .el-checkbox { + font-size: 16px; + + .is-indeterminate { + .el-checkbox__inner { + &::before { + top: 6px; + } + } + } + + .el-checkbox__inner { + width: 16px; + height: 16px; + + &::after { + left: 5px; + top: 2px; + } + } + } + + /* 表格单选框 */ + .el-radio { + .el-radio__input { + &.is-checked { + + .el-radio__inner { + border: 1px solid @color-purple; + background-color: #fff; + + &::after { + background-color: @color-purple; + width: 8px; + height: 8px; + } + } + } + + .el-radio__inner { + width: 16px; + height: 16px; + } + } + + input { + width: 100%; + height: 100%; + } + } + + .el-button--text { + padding: 0; + margin-left:0; + } + } + + &-pagination{ + padding: 10px; + text-align: right; + } +} + +// 页面loading展示 +.el-loading-spinner { + .el-icon-loading { + font-size: 32px; + } +} \ No newline at end of file diff --git a/src/Tabulation.vue b/src/Tabulation.vue new file mode 100644 index 0000000..428abe7 --- /dev/null +++ b/src/Tabulation.vue @@ -0,0 +1,887 @@ + + + + \ No newline at end of file diff --git a/src/components/renderComponent.vue b/src/components/renderComponent.vue new file mode 100644 index 0000000..49136c8 --- /dev/null +++ b/src/components/renderComponent.vue @@ -0,0 +1,43 @@ + + diff --git a/src/components/renderCustomComponent.vue b/src/components/renderCustomComponent.vue new file mode 100644 index 0000000..958a5cb --- /dev/null +++ b/src/components/renderCustomComponent.vue @@ -0,0 +1,53 @@ + + diff --git a/src/components/tabulation-column.vue b/src/components/tabulation-column.vue new file mode 100644 index 0000000..8309be6 --- /dev/null +++ b/src/components/tabulation-column.vue @@ -0,0 +1,173 @@ + + + diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..146ee0d --- /dev/null +++ b/src/index.js @@ -0,0 +1,12 @@ +/* + * @Author: CasualMing + * @Date: 2021-11-24 09:29:04 + * @LastEditTime: 2021-11-30 18:47:08 + * @Description: 注册为vue插件 + * @FilePath: \sinosun-operation-ui\components\Tabulation\index.js + */ +import Tabulation from './Tabulation.vue'; + +Tabulation.install = Vue => Vue.component(Tabulation.name, Tabulation); + +export default Tabulation; \ No newline at end of file diff --git a/src/mixin/base.js b/src/mixin/base.js new file mode 100644 index 0000000..548594e --- /dev/null +++ b/src/mixin/base.js @@ -0,0 +1,143 @@ +/* + * @Author: CasualMing + * @Date: 2021-11-24 10:06:56 + * @LastEditTime: 2021-12-10 14:52:55 + * @Description: 基本的数据接收及方法 + * @FilePath: \sinosun-operation-ui\components\Tabulation\mixin\base.js + */ +export default { + props: { + /** + * @description 表头数据及配置 + */ + columns: { + type: Array, + required: true + }, + /** + * @description 表格加载配置 + */ + loadingOptions: { + type: Object, + default: null + }, + /** + * @description 表格配置 + */ + options: { + type: Object, + default: () => {} + }, + }, + methods: { + /** + * @description 单元格 hover 进入时触发的事件 + */ + handleCellMouseEnter(row, column, cell, event) { + this.$emit('cell-mouse-enter', row, column, cell, event) + }, + /** + * @description 单元格 hover 退出时触发的事件 + */ + handleCellMouseLeave(row, column, cell, event) { + this.$emit('cell-mouse-leave', row, column, cell, event) + }, + /** + * @description 单元格点击时触发的事件 + */ + handleCellClick(row, column, cell, event) { + this.$emit('cell-click', row, column, cell, event) + }, + /** + * @description 单元格双击时触发的事件 + */ + handleCellDblclick(row, column, cell, event) { + this.$emit('cell-dblclick', row, column, cell, event) + }, + /** + * @description 行点击时触发的事件 + */ + handleRowClick(row, event, column) { + this.$emit('row-click', row, event, column) + }, + /** + * @description 行右键点击时触发的事件 + */ + handleRowContextmenu(row, event) { + this.$emit('row-contextmenu', row, event) + }, + /** + * @description 行双击时触发的事件 + */ + handleRowDblclick(row, event) { + this.$emit('row-dblclick', row, event) + }, + /** + * @description 表头点击时触发的事件 + */ + handleHeaderClick(column, event) { + this.$emit('header-click', column, event) + }, + /** + * @description 表头右键点击时触发的事件 + */ + handleHeaderContextmenu(column, event) { + this.$emit('header-contextmenu', column, event) + }, + + /** + * @description table 表格内容区域局部滚动 + */ + initTableBodyScroll() { + this.$nextTick(() => { + const { $el: El } = this; + if (!El) return false; + // 观察器的配置(需要观察什么变动) + const config = { + attributes: true, // 开启监听属性 + childList: true, // 开启监听子节点 + subtree: true, // 开启监听子节点下面的所有节点 + }; + // 当观察到变动时执行的回调函数 + const callback = (mutationsList) => { + for (let mutation of mutationsList) { + const { type, target, attributeName } = mutation; + if (type === 'attributes') { + if (!target[attributeName] || !target[attributeName].length) return false; + const attribute = target[attributeName][0]; + const changeAttr = getComputedStyle(target)[attribute]; + if (typeof attribute === 'string' && attribute.toLowerCase() === 'max-height') { + this.$refs.elTable && this.$refs.elTable.layout.setMaxHeight(changeAttr); + } + } + } + }; + // 创建一个观察器实例并传入回调函数 + const observer = new MutationObserver(callback); + // 以上述配置开始观察目标节点 + observer.observe(El, config); + }) + } + }, + mounted() { + const { options, handleAttribute } = this; + if (handleAttribute(options.bodyScroll, false)) { + this.initTableBodyScroll(); + } + window.onresize = () => { + if (handleAttribute(options.bodyScroll, false)) { + this.initTableBodyScroll(); + } + } + }, + watch: { + options: { + handler: function(v) { + if (v && v.bodyScroll) { + this.initTableBodyScroll(); + } + }, + immediate: true + } + } +} \ No newline at end of file diff --git a/src/mixin/data.js b/src/mixin/data.js new file mode 100644 index 0000000..5039f47 --- /dev/null +++ b/src/mixin/data.js @@ -0,0 +1,117 @@ +/* + * @Author: CasualMing + * @Date: 2021-11-24 16:35:07 + * @LastEditTime: 2022-02-10 08:31:31 + * @Description: + * @FilePath: \tabulation\src\mixin\data.js + */ +import { deepCopy, removedDuplicate, formatJsonObj } from "../utils/lib"; +import _get from 'lodash.get' +import _set from 'lodash.set' + +export default { + props: { + /** + * @description 表格数据 当页列表需要渲染的数据 + */ + data: { + type: Array, + default: () => { return [] }, + required: true + }, + }, + data() { + return { + + /** + * @description 表格内部数据 + */ + crudTableData: [], + /** + * @description 拷贝的单选配置数据 + */ + indexRowCopy: null, + /** + * @description 拷贝的多选配置数据 + */ + selectionRowCopy: null + } + }, + computed: { + crudTableDataLength() { + return this.crudTableData.length + } + }, + mounted() { + this.handleDataChange() + }, + methods: { + /** + * @description lodash.get + */ + _get, + /** + * @description lodash.set + */ + _set, + /** + * @description 同步外部表格数据到crudTableData内部 + */ + handleDataChange() { + const { selectionRowCopy, indexRowCopy, options, handleAttribute } = this; + + const scrollToTopMargin = handleAttribute(options.scrollToTopMargin, 0); + const isScrollToTop = handleAttribute(options.scrollToTop, true); + const scrollDom = this.$root.$el && this.$root.$el.querySelector && this.$root.$el.querySelector('.view-wrapper'); + if (scrollDom && isScrollToTop) { + this.$nextTick(() => { + this.scrollToTop(scrollDom, 1000, scrollToTopMargin); + }) + } + let list = this.data ? deepCopy(this.data) : []; + list = list.map(t => { + return { + ...formatJsonObj(t), + itemActived: false, + } + }) + this.crudTableData = selectionRowCopy ? + removedDuplicate(list, selectionRowCopy.idKey) : + indexRowCopy ? + removedDuplicate(list, indexRowCopy.idKey) : + list; + }, + /** + * @description 排序状态 + */ + handleSortChange({ column, prop, order }) { + const elTable = this.$refs.elTable || null; + this.$emit('sort-change', { column, prop, order, ref: elTable }) + }, + + /** + * @description 表格数据进行改变时,进行向上传递事件 + * @param {any} scope 表格 + * @param {any} item + */ + cellDataChange(scope, item) { + const { $index, row } = scope; + const { key } = item; + this.$emit("cell-data-change", { + rowIndex: $index, + key, + value: row[key], + row, + }); + }, + }, + watch: { + data: { + handler: function() { + this.handleDataChange(); + }, + immediate: true, + deep: true, + } + } +} \ No newline at end of file diff --git a/src/mixin/handleRow.js b/src/mixin/handleRow.js new file mode 100644 index 0000000..0a4e0c0 --- /dev/null +++ b/src/mixin/handleRow.js @@ -0,0 +1,51 @@ +/* + * @Author: CasualMing + * @Date: 2021-11-24 11:20:20 + * @LastEditTime: 2021-12-08 11:16:34 + * @Description: 列表是否含有操作项 + * @FilePath: \sinosun-operation-ui\components\Tabulation\mixin\handleRow.js + */ +export default { + props: { + /** + * @description 操作项 + */ + rowHandle: { + type: Object, + default: () => null + } + }, + methods: { + /** + * @description 控制操作列 show 的方法 + */ + handleShow(show = true, index, row) { + if (typeof show === 'boolean') { + return show + } else if (typeof show === 'function') { + return show(index, row) + } + return Boolean(show) + }, + /** + * @description 控制操作列 disabled 的方法 + */ + handleDisabled(disabled = false, index, row) { + if (typeof disabled === 'boolean') { + return disabled + } else if (typeof disabled === 'function') { + return disabled(index, row) + } + return Boolean(disabled) + } + }, + watch: { + rowHandle: { + handler: function(v) { + return v; + }, + immediate: true, + deep: true + } + } +} \ No newline at end of file diff --git a/src/mixin/handleSelect.js b/src/mixin/handleSelect.js new file mode 100644 index 0000000..8c3b8c8 --- /dev/null +++ b/src/mixin/handleSelect.js @@ -0,0 +1,173 @@ +/* + * @Author: CasualMing + * @Date: 2021-11-29 16:17:47 + * @LastEditTime: 2022-02-10 08:33:19 + * @Description: 单选多选操作相关逻辑 + * @FilePath: \tabulation\src\mixin\handleSelect.js + */ +import { deepCopy, removedDuplicate } from "../utils/lib"; +export default { + props: { + + /** + * @description 索引 + */ + indexRow: { + type: Object, + default: () => {} + }, + /** + * @description 多选 + */ + selectionRow: { + type: Object, + default: () => {} + }, + }, + methods: { + + /** + * @description 勾选数据时触发的事件 + */ + handleSelect(row) { + const { selectionRowCopy: { idKey } } = this; + let { selectionRowCopy: { selectionList } } = this; + if (row.itemActived) { + selectionList && selectionList.push(row); + } else { + selectionList = selectionList ? selectionList.filter( + (ele) => ele[idKey] != row[idKey] + ) : []; + } + selectionList = selectionList ? removedDuplicate(selectionList, idKey) : []; + + this.initSemi(); + this.initAllStatus(); + + this.$emit('select', row.itemActived, row, selectionList); + + this.handleSelectionChange(selectionList) + }, + /** + * @description 勾选全选时触发的事件 + */ + handleSelectAll(selection) { + const { crudTableData, selectionRowCopy: { selectionList, idKey } } = this; + let list = [] + if (selection) { + list = deepCopy(selectionList).concat(crudTableData); + list = removedDuplicate(list, idKey); + crudTableData.forEach((ele) => { + ele.itemActived = true; + }); + } else { + list = selectionList.length ? selectionList + .map((ele) => { + crudTableData.forEach((item) => { + item.itemActived = false; + if (item[idKey] === ele[idKey]) { + ele.itemActived = false; + } + }); + return ele; + }) + .filter((ele) => ele.itemActived) : []; + } + + this.$emit('select-all', selection, list); + this.handleSelectionChange(list); + + this.initSemi(false); + + this.initAllStatus(selection); + }, + + /** + * @description 复选框选择项发生变化时触发的事件 + */ + handleSelectionChange(selection) { + this.$emit('selection-change', selection) + }, + + /** + * @description 单选模式下,单选框发生变化回调 + */ + selectIndex(selection, scope) { + const { $index: index, row } = scope; + this.$emit('selection-index', selection, index, row); + }, + /** + * @description 多选模式下选中数据配合表格回显 + */ + initTableSelectionActived() { + const { crudTableData, selectionRowCopy } = this; + const { selectionList, idKey } = selectionRowCopy || {}; + if (crudTableData && crudTableData.length) { + crudTableData.forEach((item) => { + item.itemActived = false; + selectionList && selectionList.length ? selectionList.forEach((ele) => { + if (ele[idKey] == item[idKey]) { + item.itemActived = true; + } + }) : item.itemActived = false;; + }); + this.initSemi(); + this.initAllStatus(); + } + }, + /** + * @description 初始化全选按钮是否展示半选状态 + */ + initSemi() { + const { getActivedNum, crudTableDataLength } = this; + const isSemi = arguments.length ? + !!arguments[0] : + !!getActivedNum && (getActivedNum < crudTableDataLength); + this.$set(this.selectionRowCopy, 'isSemi', isSemi); + }, + + /** + * @description 初始化全选按钮是否全选 + */ + initAllStatus() { + const { getActivedNum, crudTableDataLength } = this; + const isAllActived = arguments.length ? !!arguments[0] : + !!crudTableDataLength && (crudTableDataLength === getActivedNum); + this.$set(this.selectionRowCopy, 'isAllActived', isAllActived); + }, + }, + computed: { + + /** + * @description 获取当前页选中数量 + */ + getActivedNum() { + const { crudTableData } = this + return ( + crudTableData && + crudTableData.filter((ele) => ele.itemActived).length + ); + }, + }, + watch: { + indexRow: { + handler: function(newV, oldV) { + if (newV !== oldV) { + this.indexRowCopy = newV; + } + }, + immediate: true, + deep: true, + }, + selectionRow: { + handler: function(newV, oldV) { + if (newV !== oldV) { + this.selectionRowCopy = newV; + this.initTableSelectionActived(); + } + }, + immediate: true, + deep: true, + }, + } +} \ No newline at end of file diff --git a/src/mixin/pagination.js b/src/mixin/pagination.js new file mode 100644 index 0000000..06e6aad --- /dev/null +++ b/src/mixin/pagination.js @@ -0,0 +1,52 @@ +/* + * @Author: CasualMing + * @Date: 2021-11-24 10:25:43 + * @LastEditTime: 2021-12-10 09:20:31 + * @Description: 表格分页处理 + * @FilePath: \sinosun-operation-ui\components\Tabulation\mixin\pagination.js + */ +export default { + props: { + /** + * @description 表格数据 + */ + pagination: { + type: Object, + default: () => null + } + }, + methods: { + /** + * @description 每页条数改变 + */ + handlePaginationSizeChange(pageSize) { + this.$emit('size-change', pageSize) + }, + /** + * @description 当前页码改变 + */ + handlePaginationCurrentChange(currentPage) { + this.$emit('current-change', currentPage) + }, + /** + * @description 上一页 + */ + handlePaginationPrevClick(currentPage) { + this.$emit('prev-click', currentPage) + }, + /** + * @description 下一页 + */ + handlePaginationNextClick(currentPage) { + this.$emit('next-click', currentPage) + }, + }, + watch: { + pagination: { + handler: function(v) { + return v; + }, + deep: true, + } + } +} \ No newline at end of file diff --git a/src/mixin/scroll.js b/src/mixin/scroll.js new file mode 100644 index 0000000..d490a75 --- /dev/null +++ b/src/mixin/scroll.js @@ -0,0 +1,95 @@ +/* + * @Author: CasualMing + * @Date: 2021-11-25 10:21:25 + * @LastEditTime: 2022-02-10 08:33:56 + * @Description: 表格滚动相关方法及配置 + * @FilePath: \tabulation\src\mixin\scroll.js + */ +import { backTop } from '../utils/lib' +export default { + props: { + /** + * @description 表格进行加载更多时的配置 + */ + loadMoreOptions: { + type: Object, + default: null, + } + }, + methods: { + loadMore() { + const { loadMoreOptions, handleAttribute } = this; + const isScroll = handleAttribute(loadMoreOptions.isScroll, false); + if (isScroll) { + this.$emit("load-more"); + } + }, + /** + * @description 滚动表格内容区域到距离顶部指定的距离 + * @param {any} distance + */ + tableScrollTop(distance, top = 0) { + this.$nextTick(() => { + if (this.$refs.elTable) { + const { bodyWrapper } = this.$refs.elTable || {}; + if (bodyWrapper._isVue) { + backTop(bodyWrapper.$el, distance, top); + } else { + backTop(bodyWrapper, distance, top) + } + } + }) + }, + /** + * @description 滚动到距离指定元素父元素的指定高度 + * @param {any} distance 每次滚动多少 + * @param {any} top 距离顶部多少px + */ + scrollToTop(taget, distance = 50, top) { + this.$nextTick(() => { + if (!taget) return false; + if (taget._isVue) { + backTop(taget.$el, distance, top); + } else { + backTop(taget, distance, top) + } + }) + }, + }, + + directives: { + // 滚动到列表底部 + scrollToEnd: { + bind(el, binding, vnode) { + let isOpen = true; + + const scrollEle = el.querySelector(".el-table__body-wrapper"); // 表格滚动元素 + const that = vnode.context; + that.scrollEle = scrollEle; + scrollEle.addEventListener("scroll", function() { + if (!isOpen) return; + isOpen = false; + setTimeout(() => { + isOpen = true; + const { scrollTop, scrollHeight, clientHeight } = this; + const isScrollToEnd = scrollTop && scrollHeight - scrollTop - clientHeight <= 50; + if (isScrollToEnd) { + binding.value(); + } + }, 700); + }); + }, + }, + }, + + computed: { + tabeleScollClass() { + return ` + el-table--scrollable-y + el-table--fluid-height + el-table--fit + el-table--enable-row-hover + el-table--enable-row-transition`; + }, + } +} \ No newline at end of file diff --git a/src/mixin/utils.js b/src/mixin/utils.js new file mode 100644 index 0000000..c1a3635 --- /dev/null +++ b/src/mixin/utils.js @@ -0,0 +1,53 @@ +import { isEmpty } from '../utils/lib'; + +export default { + methods: { + + /** + * @description 控制是否展示 + */ + handleShow(show = true, ...args) { + if (typeof show === 'boolean') { + return show + } else if (typeof show === 'function') { + return show(...args) + } + return Boolean(show) + }, + /** + * @description 控制是否disabled + */ + handleDisabled(disabled = false, ...args) { + if (typeof disabled === 'boolean') { + return disabled + } else if (typeof disabled === 'function') { + return disabled(...args) + } + return Boolean(disabled) + }, + + /** + * @description 组件属性默认值 + */ + handleAttribute(attribute, defaultValue) { + if (attribute === false || attribute === 0 || attribute === '') { + return attribute + } + return attribute || defaultValue + }, + + /** + * @description 用于判断是否是空数组或者空对象 + */ + isEmpty, + + + /** + * @description 是否是element的内置icon + */ + isEleIcon(str) { + if (!str) return true; + typeof str == 'string' ? str.includes("el-icon-") : false + }, + } +} \ No newline at end of file diff --git a/src/utils/lib.js b/src/utils/lib.js new file mode 100644 index 0000000..49fe737 --- /dev/null +++ b/src/utils/lib.js @@ -0,0 +1,251 @@ +/* + * @Author: CasualMing + * @Date: 2020-12-09 18:38:14 + * @LastEditTime: 2022-02-10 08:34:06 + * @Description: 提供项目中经常用到的公共函数 + */ + +// 获取url中的参数 +export function getUrlParams(url, key) { + url = url || window.location.href; + let regexP = /[^#&\?]+=[^#&\?]*/ig, + res = {}; + let ms = url.match(regexP); + if (ms) { + for (let i = 0; i < ms.length; i++) { + let arr = ms[i].split('='); + res[arr[0]] = decodeURI(arr[1]); + } + } + if (key) { + return res[key]; + } + return res; +} + +// 格式化一个JSON对象 +export function formatJsonObj(target) { + const result = {}; + + for (const attr in target) { + if (!/^_\w+_$/.test(attr)) { + let value = target[attr]; + + switch (Object.prototype.toString.call(value)) { + case "[object Function]": + continue; + case "[object Object]": + value = formatJsonObj(value); + break; + case "[object Array]": + value = value.map(e => { + if (Object.prototype.toString.call(e) == '[object Object]') { + return formatJsonObj(e); + } else { + return e; + } + }); + break; + } + + if (attr[0] == '_') { + result[attr.substring(1)] = value; + } else { + result[attr] = value; + } + } + } + + return result; +} + +function formater(fmt, time) { + let date = time || this; + var showDayArr = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; + var o = { + "M+": date.getMonth() + 1, // 月份 + "d+": date.getDate(), // 日 + "E+": showDayArr[date.getDay()], // 周 + "D+": date.getDate(), // 日 + "H+": date.getHours(), // 小时 + "m+": date.getMinutes(), // 分 + "s+": date.getSeconds(), // 秒 + "q+": Math.floor((date.getMonth() + 3) / 3), // 季度 + "S": date.getMilliseconds() // 毫秒 + }; + if (/(y+)/i.test(fmt)) { + fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length)); + } + for (var k in o) { + if (new RegExp("(" + k + ")").test(fmt)) { + fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); + } + } + return fmt; +} + + +// 时间格式化 +(Date).prototype.format = formater + /** + * 按照指定格式格式化时间 + * @param {String} formater 要格式的格式 默认 yyyy-mm-dd + * @param {Date} t 要格式的时间,如果不传默认格式化当前时间 + * @param {Boolean} isDecimal 是否是十位 + * dateFormater('YYYYMMDD') ==> 20200306 + * dateFormater('YYMMDD') ==> "200306" + */ +export function dateFormat(t, format = 'YYYY-MM-DD', isDecimal = false) { + // 是否是IE浏览器 + const isIE = "-ms-scroll-limit" in document.documentElement.style && "-ms-ime-align" in document.documentElement.style; + const isNumber = getClass(t) === 'Number'; + const isString = getClass(t) === 'String'; + // const isDate = getClass(t) === 'Date'; + // 兼容IE 在进行字符串时间格式化的时候,避免出现NaN操作 + const num = isDecimal ? + isNumber ? + t * 1000 : + t : + t + const tDate = isIE ? + isString ? + new Date(num.replaceAll('-', '/')) : + new Date(num) : + new Date(num); + return formater(format, tDate) +} + +// 获取当前函数的节流函数 +export function throttle(fn, delay = 300) { + let open = true; + return function() { + const args = arguments; + if (open) { + open = false; + setTimeout(() => { + fn.apply(this, args); + open = true; + }, delay); + } + } +} + +// 获取当前函数的防抖函数 +export function debounce(fn, delay = 400) { + let timer = null; + return function() { + const args = arguments; + clearTimeout(timer); + timer = setTimeout(() => { + fn.apply(this, args); + }, delay); + } +} + +// 获取类型名称 +export function getClass(target) { + return Object.prototype.toString.call(target).match(/^\[object\s(.*)\]$/)[1]; +} + +// 数组去重 +export function removedDuplicate(arr, key) { + const newArr = [], + tempArr = []; + if (arr.length == 0) { + return arr; + } else { + if (key) { + for (let i = 0; i < arr.length; i++) { + if (!tempArr[arr[i][key]]) { + newArr.push(arr[i]); + tempArr[arr[i][key]] = true; + } + } + return newArr; + } else { + for (let i = 0; i < arr.length; i++) { + if (!tempArr[arr[i]]) { + newArr.push(arr[i]); + tempArr[arr[i]] = true; + } + } + return newArr; + } + } +} + +// 判断数组或者对象是否是空白 +export function isEmpty(param) { + const type = getClass(param); + if (type === 'Array') { + return param.filter((el) => { + const type = getClass(el); + const typeMap = new Map([ + ['String', el], + ['Object', Object.keys(el).length], + ['Array', isEmpty(el)], + ]); + return typeMap.get(type); + }).length + } + if (type === 'Object') { + return Object.keys(param).length + } + return false +} + +/** + * 深度克隆 + * @date 2021-03-31 + * @param {any} obj 需要克隆的值 + * @param {WeakMap} cache 防止循环引用 + * @returns {any} + */ +export function deepCopy(obj, cache = new WeakMap()) { + if (!(obj instanceof Object)) return obj; + // 防止循环引用 + if (cache.get(obj)) return cache.get(obj) + // 支持函数 + if (obj instanceof Function) { + return function() { + return obj.apply(this, arguments) + } + } + // 支持日期 + if (obj instanceof Date) return new Date(obj) + // 支持正则对象 + if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags) + // 还可以增加其他对象,比如:Map, Set等,根据情况判断增加即可 + + // 数组是 key 为数字素银的特殊对象 + const res = Array.isArray(obj) ? [] : {} + // 缓存 copy 的对象,用于处理循环引用的情况 + cache.set(obj, res) + + Object.keys(obj).forEach((key) => { + if (obj[key] instanceof Object) { + res[key] = deepCopy(obj[key], cache) + } else { + res[key] = obj[key] + } + }); + return res +} + +/** + * @description 对应的元素滚动到顶部,带动画 + * @param {any} distance 每次滚动多少 + */ +export function backTop(dom, distance = 50, top = 0) { + let timer = null; + cancelAnimationFrame(timer); + timer = requestAnimationFrame(function fn() { + let oTop = dom.scrollTop; + if (oTop > top) { + dom.scrollTop = oTop - distance >= 0 ? oTop - distance : top; + timer = requestAnimationFrame(fn); + } else { + cancelAnimationFrame(timer); + } + }); +} \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..33aacaa --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,111 @@ +var path = require('path') +var webpack = require('webpack') + +module.exports = { + entry: './src/index.js', + output: { + path: path.resolve(__dirname, './dist'), + publicPath: '/dist/', + filename: 'd2-crud.js', + library: 'D2Crud', + libraryTarget: 'umd', + umdNamedDefine: true + }, + module: { + rules: [ + { + test: /\.css$/, + use: [ + 'vue-style-loader', + 'css-loader' + ], + }, + { + test: /\.scss$/, + use: [ + 'vue-style-loader', + 'css-loader', + 'sass-loader' + ], + }, + { + test: /\.sass$/, + use: [ + 'vue-style-loader', + 'css-loader', + 'sass-loader?indentedSyntax' + ], + }, + { + test: /\.vue$/, + loader: 'vue-loader', + options: { + loaders: { + // Since sass-loader (weirdly) has SCSS as its default parse mode, we map + // the "scss" and "sass" values for the lang attribute to the right configs here. + // other preprocessors should work out of the box, no loader config like this necessary. + 'scss': [ + 'vue-style-loader', + 'css-loader', + 'sass-loader' + ], + 'sass': [ + 'vue-style-loader', + 'css-loader', + 'sass-loader?indentedSyntax' + ] + } + // other vue-loader options go here + } + }, + { + test: /\.js$/, + loader: 'babel-loader', + exclude: /node_modules/ + }, + { + 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 + }) + ]) +}