You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

929 lines
28 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<script>
import axios from 'axios';
import {getToken} from "@/utils/auth";
export default {
props: {
//请求相关
action:[String,Function], //String传入请求地址 Function传入返回promise的方法
reqOpt:Object, //请求参数配置
resProp:{
type:String,
default:'data'
},//请求后需要获取表格数据键名
//操作权限
auths: {
type: Array,
default: () => ['edit','delete'],
},
//table props
/**
* @type {Array}
* [
* {
* type: ("selection"|"index"|"expand"),
* index: [Number,Function(index)], //type=index,通过传递 index 属性来自定义索引
* columnKey: "", //column 的 key如果需要使用 filter-change 事件,则需要此属性标识是哪个 column 的筛选条件
* label: "",
* prop: "",
* width: "",
* minWidth: "",
* fixed: ("true"| "left"| "right"),
* renderHeader:(h, { column, $index}) => {
* },
* sortable:[Boolean,String] (true|false|"custom"),//custom需监听 Table 的 sort-change 事件
* sortMethod: (a,b) => {}, //对数据进行排序的时候使用的方法,仅当 sortable 设置为 true 的时候有效,需返回一个数字,和 Array.sort 表现一致
* sortBy:[String,Array,Function(row, index)],//指定数据按照哪个属性进行排序,仅当 sortable 设置为 true 且没有设置 sort-method 的时候有效。如果 sort-by 为数组,则先按照第 1 个属性排序,如果第 1 个相等,再按照第 2 个排序,以此类推
* sortOrders:[Array] (['ascending', 'descending', null]),//数据在排序时所使用排序策略的轮转顺序,仅当 sortable 为 true 时有效
* resizable:[Boolean], //需要在 el-table 上设置 border 属性为真
* formatter:[Function(row, column, cellValue, index)],
* showOverflowTooltip:[Boolean],
* align: ("left","center","right"),
* headerAlign: ("left","center","right"),
* className:[String],
* labelClassName:[String],
* selectable:[Function(row, index)],//仅对 type=selection 的列有效,类型为 FunctionFunction 的返回值用来决定这一行的 CheckBox 是否可以勾选
* reserveSelection:[Boolean],//仅对 type=selection 的列有效,类型为 Boolean为 true 则会在数据更新之后保留之前选中的数据(需指定 row-key
* filters:[Array[{ text, value }]],
* filterPlacement:[String],
* filterMultiple:[Boolean],
* filterMethod:[Function(value, row, column)],
* filteredValue:[Array]
* }
* ]
*/
tableItem: {
type: Array,
default: () => [],
},
//数据
list: {
type: Array,
default: () => [],
},
height: {
type: [String, Number],
},
maxHeight: [String, Number],
//斑马纹
stripe: {
type: Boolean,
default: false,
},
//是否带有纵向边框
border: {
type: Boolean,
default: true,
},
size: String, // medium / small / mini
fit: {
type: Boolean,
default: true,
},
showHeader: {
type: Boolean,
default: true,
},
highlightCurrentRow: {
type: Boolean,
default: true,
},
currentRowKey: [String, Number],
rowClassName: [Function, String],
rowStyle: [Function, Object],
cellClassName: [Function, String],
cellStyle: [Function, Object],
headerRowClassName: [Function, String],
headerRowStyle: [Function, Object],
headerCellClassName: [Function, String],
headerCellStyle: [Function, Object],
rowKey: {
type: [String, Function],
default: "id",
},
emptyText: String,
defaultExpandAll: {
type: Boolean,
default: true,
},
expandRowKeys: Array, //可以通过该属性设置 Table 目前的展开行,需要设置 row-key 属性才能使用,该属性为展开行的 keys 数组。
defaultSort: Object, //默认的排序列的 prop 和顺序。它的prop属性指定默认的排序的列order指定默认排序的顺序order: ascending, descending
tooltipEffect: String,
showSummary: {
type: Boolean,
default: false,
},
sumText: {
type: String,
default: "合计",
},
summaryMethod: Function, //Function({ columns, data })
spanMethod: Function,
selectOnIndeterminate: Boolean,
//展示树形数据时,树节点的缩进
indent: {
type: Number,
default: 18,
},
lazy: Boolean,
load: Function, //Function(row, treeNode, resolve)
treeProps: {
type: Object,
default: () => {
return { children: "children", hasChildren: "hasChildren" };
},
},
tableStyle: {
type: Object,
default: () => {
return { width: "100%", marginBottom: "20px" };
},
},
btnWidth: {
type: Number,
default: 140,
},
defaultBtnWidth: {
type: Number,
default: 180,
},
// 分页相关
isPage: {
type: Boolean,
default: true,
},
total: {
type: Number,
default: 0,
},
pageSize: {
type: Number,
},
pageSizeOpts: {
type: Array,
default: () => [10, 20, 30, 40],
},
showSizer: {
type: Boolean,
default: true,
},
},
data() {
return {
isBtns: false,
tableHeight: null,
//document.documentElement.clientHeight -50 -37 - 20- 25 - 76,
isShowPage: true,
checkTable:this.tableItem.map((item)=> item?.prop),
loading: false,
visibleBtn: true, //操作按钮显示
totalData:0,
listData:[],
selectOpt:{
page:1,
page_size:10,
sort_name:'',
sort_type:''
}
};
},
methods: {
//方法
initLoad() {
let clientHeight = document.documentElement.clientHeight;
let lxheader = document.querySelector('.v-header').getBoundingClientRect()
let lxHeader_height = lxheader.height + 25; //查询 头部
let paginationHeight = 37; //分页的高度
let topHeight = 50; //页面 头部
this.tableHeight =
clientHeight - lxHeader_height - topHeight - paginationHeight - 20 - 25;
// console.log(this.tableHeight)
},
getTableData(isRefresh = false){
if(isRefresh){
this.selectOpt.page = 1
}
switch (typeof this.action){
case "string":
this.loading = true
axios({
baseURL:process.env.VUE_APP_BASE_API,
url:this.action,
headers:{
Authorization:"Bearer " + getToken()
},
params:(this.reqOpt.method && this.reqOpt.method === 'get') ? this.selectOpt : '',
data:(this.reqOpt.method && this.reqOpt.method === 'post') ? this.selectOpt : '',
...this.reqOpt
}).then(res => {
this.listData = this.getByStrkey(res.data,this.resProp)
this.totalData = res.data.total
setTimeout(() => {
this.loading = false
},300)
}).catch(err => {
console.error(err)
this.loading = false
})
break;
case "function":
this.loading = true
this.action(false,{
...this.selectOpt,
...this.reqOpt
}).then(res => {
this.listData = this.getByStrkey(res,this.resProp)
this.totalData = res.total
setTimeout(() => {
this.loading = false
},300)
}).catch(err => {
console.error(err)
this.loading = false
})
break;
}
},
getByStrkey(obj,str){
if(!str) return obj
let res = this.deepCopy(obj);
let keys = str.split('.')
keys.forEach(key => {
res = res[key]
})
return res;
},
deepCopy(data){
//string,number,bool,null,undefined,symbol
//object,array,date
if (data && typeof data === "object") {
//针对函数的拷贝
if (typeof data === "function") {
let tempFunc = data.bind(null);
tempFunc.prototype = this.deepCopy(data.prototype);
return tempFunc;
}
switch (Object.prototype.toString.call(data)) {
case "[object String]":
return data.toString();
case "[object Number]":
return Number(data.toString());
case "[object Boolean]":
return new Boolean(data.toString());
case "[object Date]":
return new Date(data.getTime());
case "[object Array]":
let arr = [];
for (let i = 0; i < data.length; i++) {
arr[i] = this.deepCopy(data[i]);
}
return arr;
//js自带对象或用户自定义类实例
case "[object Object]":
let obj = {};
for (let key in data) {
//会遍历原型链上的属性方法可以用hasOwnProperty来控制 obj.hasOwnProperty(prop)
obj[key] = this.deepCopy(data[key]);
}
return obj;
}
} else {
//string,number,bool,null,undefined,symbol
return data;
}
},
sortListener({column, prop, order}){
this.selectOpt.sort_name = prop
switch (order){
case "ascending":
this.selectOpt.sort_type = 'ASC'
break;
case "descending":
this.selectOpt.sort_type = 'DESC'
break;
default:
this.selectOpt.sort_type = ''
}
this.getTableData()
},
//element table方法
clearSelection() {
this.$refs.table.clearSelection();
},
toggleRowSelection(row) {
this.$nextTick(() => {
this.$refs.table.toggleRowSelection(row);
});
},
toggleAllSelection() {
this.$refs.table.toggleAllSelection();
},
toggleRowExpansion(row, expanded = true) {
this.$refs.table.toggleRowExpansion(row, expanded);
},
setCurrentRow(row) {
this.$refs.table.toggleRowExpansion(row);
},
clearSort() {
this.$refs.table.clearSort();
},
clearFilter() {
this.$refs.table.clearFilter();
},
doLayout() {
this.$refs.table.doLayout();
},
sort() {
this.$refs.table.sort();
},
//table通讯事件
delete(row, type) {
this.$emit(type, row);
},
editor(row, type) {
this.$emit(type, row);
},
select(selection, row) {
this.$emit("select", selection, row);
},
selectAll(selection) {
this.$emit("select-all", selection);
},
selectionChange(selection) {
this.$emit("selection-change", selection);
},
cellMouseEnter(row, column, cell, event) {
this.$emit("cell-mouse-enter", { row, column, cell, event });
},
cellMouseLeave(row, column, cell, event) {
this.$emit("cell-mouse-leave", { row, column, cell, event });
},
cellClick(row, column, cell, event) {
this.$emit("cell-click", { row, column, cell, event });
},
cellDblclick(row, column, cell, event) {
this.$emit("cell-dblclick", { row, column, cell, event });
},
rowClick(row, column, event) {
this.$emit("row-click", { row, column, event });
},
rowContextmenu(row, column, event) {
this.$emit("row-contextmenu", { row, column, event });
},
rowDblclick(row, column, event) {
this.$emit("row-dblclick", { row, column, event });
},
headerClick(column, event) {
this.$emit("header-click", column, event);
},
headerContextmenu(column, event) {
this.$emit("header-contextmenu", column, event);
},
sortChange({ column, prop, order }) {
this.$emit("sort-change", { column, prop, order });
},
filterChange(filters) {
this.$emit("filter-change", filters);
},
currentChange(currentRow, oldCurrentRow) {
this.$emit("current-change", currentRow, oldCurrentRow);
},
headerDragend(newWidth, oldWidth, column, event) {
this.$emit("header-dragend", { newWidth, oldWidth, column, event });
},
expandChange(row, expanded) {
this.$emit("expand-change", row, expanded);
},
deleteClick(row) {
this.$emit('delete', row)
},
editorClick(row) {
this.$emit('editor', row)
},
createPage() {
if (this.isPage)
return (
<div>
<transition
enter-active-class="slide-in-bottom"
leave-to-class="slide-out-down"
>
<Page
page-size-opts={this.pageSizeOpts}
show-total={true}
current={this.selectOpt.page}
page-size={this.pageSize}
v-show={this.isShowPage}
total={this.action ? (this.totalData || this.listData.length) : this.total}
size="small"
show-elevator={true}
show-sizer={this.showSizer}
class="xy-table__page"
on={{
["on-page-size-change"]: (e) => {
if(this.action){
this.selectOpt.page_size = e
this.selectOpt.page = 1
this.getTableData()
}
this.$emit("pageSizeChange", e)
},
["on-change"]: (e) => {
if(this.action){
this.selectOpt.page = e
this.getTableData()
}
this.selectOpt.page = e
this.$emit("pageIndexChange", e)
}
}}
></Page>
</transition>
<el-popover
placement="top-start"
width="200"
trigger="hover"
scopedSlots={{
default: () => {
return (
<el-checkbox-group v-model={this.checkTable}>
{this.$props?.tableItem?.map((item, index) => {
return item.type !== 'expand' ? (
<el-checkbox label={item.prop} key={item.prop}>
{item.label}
</el-checkbox>
) : ''
})}
</el-checkbox-group>
);
},
}}
>
<el-button
slot="reference"
size="mini"
type="primary"
class="xy-table__setting"
icon="el-icon-s-tools"
circle
style={{
padding: "3px",
}}
></el-button>
</el-popover>
</div>
);
},
isCreateAuthBtns() {
let _this = this;
let res = (
<el-table-column
key={`xy-table-btn`}
fixed="right"
label="操作"
width={_this.defaultBtnWidth}
minWidth={_this.btnWidth}
header-align="center"
scopedSlots={{
default: (scope) => {
if (_this.auths?.length <= 0) return;
let { dom, flag } = _this.createAuthBtns(scope);
_this.isBtns = flag;
return dom;
},
}}
></el-table-column>
);
if (_this.isBtns !== 0) {
return res;
}
},
createAuthBtns(scope) {
let _this = this;
if (_this.auths?.length > 0) {
let btns = new Map();
btns.set(
"edit",
<i-button
style={{
"margin-right": "6px",
}}
type="primary"
size="small"
ghost
onClick={() => _this.editorClick(scope.row, "edit")}
>
编辑
</i-button>
);
btns.set(
"delete",
<Poptip
transfer={true}
confirm
title="确认要删除吗"
on={{ ["on-ok"]: () => _this.deleteClick(scope.row, "delete") }}
>
<i-button
style={{
"margin-right": "6px",
}}
type="error"
size="small"
ghost
>
删除
</i-button>
</Poptip>
);
let flag = 0;
let dom = (
<div
style={{
display: "flex",
"justify-content": "flex-start",
"align-items": "center",
"flex-wrap": "wrap",
}}
>
{_this.auths.map((item, index) => {
if (_this.$scopedSlots[item]) {
flag = index;
return _this.$scopedSlots[item](scope, item, index);
} else {
if (btns.get(item)) {
flag = index;
return btns.get(item);
}
}
})}
</div>
);
return { dom, flag };
}
},
},
computed: {
tableFormat() {
return this.tableItem.filter((item) => {
return this.checkTable.indexOf(item.prop) !== -1;
});
},
},
created() {
this.getTableData()
},
watch:{
total(newval){
if(newval){
this.selectOpt.page=1
}
}
},
mounted() {
this.initLoad();
},
updated() {
this.$nextTick(() => {
this.doLayout();
})
},
render(h) {
let { $scopedSlots } = this;
return (
<div class="table-tree" style={{ position: "relative" }}>
{this.tableItem && this.tableItem.length > 0 ? (
<el-table
class="v-table"
v-loading={this.loading}
ref="table"
data={this.action ? this.listData : this.list}
height={this.height ?? this.tableHeight}
max-height={this.maxHeight}
stripe={this.stripe}
border={this.border}
size={this.size}
fit={this.fit}
show-header={this.showHeader}
highlight-current-row={this.highlightCurrentRow}
current-row-key={this.currentRowKey}
row-class-name={this.rowClassName}
row-style={this.rowStyle}
cell-class-name={this.cellClassName}
cell-style={this.cellStyle}
header-row-class-name={this.headerRowClassName}
header-row-style={this.headerRowStyle}
header-cell-class-name={this.headerCellClassName}
header-cell-style={this.headerCellStyle}
row-key={this.rowKey}
empty-text={this.emptyText}
default-expand-all={this.defaultExpandAll}
expand-row-keys={this.expandRowKeys}
default-sort={this.defaultSort}
tooltip-effect={this.tooltipEffect}
show-summary={this.showSummary}
sum-text={this.sumText}
summary-method={this.summaryMethod}
span-method={this.spanMethod}
select-on-indeterminate={this.selectOnIndeterminate}
indent={this.indent}
lazy={this.lazy}
load={this.load}
tree-props={this.treeProps}
on={{
["select"]: this.select,
["select-all"]: this.selectAll,
["selection-change"]: this.selectionChange,
["cell-mouse-enter"]: this.cellMouseEnter,
["cell-mouse-leave"]: this.cellMouseLeave,
["cell-click"]: this.cellClick,
["cell-dblclick"]: this.cellDblclick,
["row-click"]: this.rowClick,
["row-contextmenu"]: this.rowContextmenu,
["row-dblclick"]: this.rowDblclick,
["header-click"]: this.headerClick,
["header-contextmenu"]: this.headerContextmenu,
["sort-change"]: this.action ? this.sortListener : this.sortChange,
["filter-change"]: this.filterChange,
["current-change"]: this.currentChange,
["header-dragend"]: this.headerDragend,
["expand-change"]: this.expandChange,
}}
>
<el-table-column
type="index"
width="50" fixed="left" align="center" >
</el-table-column>
{this.tableFormat.map((item, index) => {
if ($scopedSlots[item.prop]) {
return $scopedSlots[item.prop](item, index);
}
return (
<el-table-column
key={String(Math.random())+index}
type={item.type}
index={item.index}
column-key={String(Math.random())}
label={item.label}
prop={item.prop}
width={item.width ?? "auto"}
min-width={item.minWidth}
fixed={item.fixed ?? false}
render-header={item.renderHeader}
sortable={item.sortable}
sort-method={item.sortMethod}
sort-by={item.sortBy}
sort-orders={item.sortOrders}
resizale={item.resizale}
formatter={item.formatter}
show-overflow-tooltip={item.showOverflowTooltip ?? true}
align={item.align ?? "center"}
header-align={item.headerAlign ?? "center"}
class-name={`xy-table__row-fade ${item.className}`}
label-class-name={`xy-table__title-fade ${item.labelClassName}`}
selectable={item.selectable}
reserve-selection={item.reserveSelection}
filters={item.filters}
filter-placement={item.filterPlacement}
filter-multiple={item.filterMultiple}
filter-method={item.filterMethod}
filtered-value={item.filteredValue}
scopedSlots={
item.customFn || item.type === "expand"
? {
default(scope) {
if (item.type === "expand") {
return item.expandFn(scope);
}
if (item.customFn) {
return item.customFn(scope.row, scope);
}
},
}
: ""
}
>
{ item.multiHd
? item.multiHd.map((item1, index1) => {
return (
<el-table-column
key={`xy-table-col-multi-${item1.prop}`}
prop={`${item.pProp ? item.pProp + "." : ""}${
item1.prop
}`}
type={item1.type}
index={item1.index}
column-key={String(Math.random())}
label={item1.label}
prop={item1.prop}
width={item1.width ?? "auto"}
min-width={item1.minWidth}
fixed={item1.fixed}
render-header={item1.renderHeader}
sortable={item1.sortable ?? false}
sort-method={item1.sortMethod}
sort-by={item1.sortBy}
sort-orders={item1.sortOrders}
resizale={item1.resizale ?? false}
formatter={item1.formatter}
show-overflow-tooltip={item1.showOverflowTooltip ?? true}
align={item1.align ?? "center"}
header-align={item1.headerAlign ?? "center"}
class-name={`xy-table__row-fade ${item1.className}`}
label-class-name={`xy-table__title-fade ${item1.labelClassName}`}
selectable={item1.selectable}
reserve-selection={item1.reserveSelection}
filters={item1.filters}
filter-placement={item1.filterPlacement}
filter-multiple={item1.filterMultiple}
filter-method={item1.filterMethod}
filtered-value={item1.filteredValue}
scopedSlots={
item1.customFn
? {
default(scope1) {
return item1.customFn(scope1);
},
}
: ""
}
></el-table-column>
);
})
: ""}
</el-table-column>
);
})}
{ $scopedSlots.btns ? $scopedSlots.btns() : this.isCreateAuthBtns() }
</el-table>
) : (
<el-table height={this.height ?? this.tableHeight} />
)}
<el-backtop
target=".el-table__body-wrapper"
visibility-height={120}
bottom={100}
right={36}
></el-backtop>
{ this.createPage() }
</div>
);
},
};
</script>
<style lang="scss" scoped>
@import "../../styles/variables.scss";
::v-deep .el-table {
margin-bottom: 0 !important;
}
.xy-table__setting {
font-size: 14px;
position: absolute;
z-index: 99;
bottom: 7px;
left: 10px;
}
.xy-table__page {
display: flex;
justify-content: right;
border-bottom-right-radius: 4px;
border-bottom-left-radius: 4px;
background: rgba(140, 140, 140, 0.6);
z-index: 10;
padding: 6px 10px !important;
position: relative;
top: 0;
left: 0;
right: 0;
::v-deep .ivu-page-item,
.ivu-page-options {
background: transparent !important;
}
::v-deep .ivu-page-item a {
color: #fff;
}
::v-deep .ivu-page-item-active a {
color: $primaryColor;
}
::v-deep .ivu-page-total {
color: #fff !important;
}
::v-deep .ivu-page-options-elevator {
color: #fff !important;
}
@media (max-width: 600px) {
::v-deep .ivu-page-options {
display: none !important;
}
}
}
.slide-in-bottom {
transform-origin: 0 100%;
animation: slide-in-bottom 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
}
@keyframes slide-in-bottom {
0% {
transform: scaleY(0);
opacity: 0;
}
100% {
transform: scaleY(1);
opacity: 1;
}
}
.slide-out-down {
transform-origin: 0 100%;
animation: slide-out-down 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
}
@keyframes slide-out-down {
0% {
transform: scaleY(1);
opacity: 1;
}
100% {
transform: scaleY(0);
opacity: 0;
}
}
.icon-scale-left {
animation: icon-scale-left 0.5s forwards ease-out;
@keyframes icon-scale-left {
from {
}
to {
clip-path: polygon(0 30%, 100% 30%, 100% 100%, 0 100%);
background: rgba(140, 140, 140, 0.7);
transform: scale(0.8, 0.8) translateX(-22px) translateY(-10px)
rotate(-90deg);
}
}
}
.icon-recover {
animation: icon-recover 0.5s forwards ease-out;
@keyframes icon-recover {
from {
background: rgba(120, 120, 120, 0.8);
transform: scale(0.8, 0.8) translateX(-30px) rotate(-90deg);
}
to {
}
}
}
</style>
<style>
.xy-table__row-fade {
animation: fade-in-row 600ms;
}
@keyframes fade-in-row {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
}
}
.xy-table__title-fade {
animation: fade-in-title 600ms;
}
@keyframes fade-in-title {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
</style>