index.vue
<template>
<div class="table">
<a-table
:row-selection="
isRowSelection
? {
selectedRowKeys: selectedRowKeys,
onChange: onSelectChange,
}
: null
"
:loading="loading"
:locale="locale"
:columns="columnsData"
:components="components"
:data-source="sourceData"
:pagination="false"
:scroll="isScroll ? { x: scrollXwidth } : {}"
:defaultExpandedRowKeys="defaultExpandedRowKeys"
@change="handleTableChange"
>
<!-- 角色管理 -->
<div
slot="filterDropdown"
class="filter-dropdown"
>
<a-row>
<a-col :span="24">
<a-col :span="5">企业管理员:</a-col>
<a-col :span="19" class="num-style">{{countRoleData.adminTotal}}名</a-col>
<a-col :span="24" class="c9">(支持查看所有页面、管理演练员工、编辑模板、发布/删除演练、管理团队成员)</a-col>
</a-col>
<a-col :span="24">
<a-col :span="4">操作者:</a-col>
<a-col :span="19" class="num-style">{{countRoleData.handlersTotal}}名</a-col>
<a-col :span="24" class="c9">(支持查看数据报表、管理演练员工、编辑模板、发布演练)</a-col>
</a-col>
<a-col :span="24">
<a-col :span="4">查看者:</a-col>
<a-col :span="19" class="num-style">{{countRoleData.viewerTotal}}名</a-col>
<a-col :span="24" class="c9">(支持查看数据报表、查看演练数据)</a-col>
</a-col>
</a-row>
</div>
<icon
slot="filterIcon"
slot-scope="filtered"
name="warning-circle"
:style="{ color: filtered ? '' : undefined }"
/>
<!-- defaultHeader 为true时 表头定制 -->
<template v-for="(componentObj, key) in customTitleObj" :slot="key">
<span :key="key">
<component :is="componentObj" />
</span>
</template>
<!-- slot -->
<template
v-for="(col, index) in slotList"
:slot="col.dataIndex"
slot-scope="text, record"
>
<span :key="index">
<!-- 有定制展示某一列的走此处 -->
<template v-if="slotObj[col.dataIndex]">
<component
:is="slotObj[col.dataIndex].colComponent"
:record="record"
:text="text"
:dataIndex="col.dataIndex"
@operationFuClick="operationClick"
/>
</template>
<!-- 没有定制展示某一列的走此处 -->
<template v-else>
<template
v-if="
text === '' ||
text === null ||
text === undefined ||
typeof text === 'null' ||
typeof text === 'undefined'
"
>
<span v-if="col.config.tableType === 'overdue'">全部</span>
<span v-else-if="col.config.type != 'switch'">暂无</span>
</template>
<template v-else-if="col.config.type === 'popover'">
<a-popover placement="topLeft">
<template slot="content">
<div style="max-width: 200px">{{ text }}</div>
</template>
<div
class="ellipsis"
:style="{
'max-width': col.config.maxWidth
? col.config.maxWidth
: '90%',
}"
>
<span>{{ text }}</span>
</div>
</a-popover>
</template>
<template v-else-if="col.config.type === 'switch'">
<a-switch
:default-checked="text"
@change="
(val) => {
operationClick({}, record, {
switchValue: { dataIndex: col.dataIndex, value: val },
});
}
"
/>
</template>
<template v-else>
<span v-if="col.config.showZero">{{ text }}</span>
<span v-else>{{ text == 0 ? "暂无" : text }}</span>
</template>
</template>
</span>
</template>
<!-- 操作栏定制 -->
<template slot="operation" slot-scope="text, record">
<template v-if="operationObj[record.op_id]">
<span
v-for="(item, index) in operationObj[record.op_id]"
:key="index + 'operation'"
class="table-icon_wrap"
>
<!-- 有定制化按钮 -->
<template
v-if="
item.operationComName && optationNameObj[item.operationComName]
"
>
<!-- //权限判断(buttonHash[item.buttonHash] || !item.buttonHash) -->
<!-- v-if="buttonHash(item.buttonHash) || !item.buttonHash" -->
<component
:is="optationNameObj[item.operationComName]"
:record="record"
:item="item"
@operationFuClick="operationClick"
/>
</template>
<template v-else-if="item.operationComponent">
<!-- //权限判断(buttonHash[item.buttonHash] || !item.buttonHash) -->
<!-- v-if="buttonHash(item.buttonHash) || !item.buttonHash" -->
<component
:is="item.operationComponent"
:record="record"
:item="item"
@operationFuClick="operationClick"
/>
</template>
<!-- 默认 -->
<template v-else>
<a-tooltip>
<template slot="title">{{ item.name }}</template>
<!-- v-if="buttonHash(item.buttonHash) || !item.buttonHash" -->
<div
class="option-btn"
@click="operationClick(item, record)"
>
<icon :name="item.icon" />
</div>
</a-tooltip>
</template>
</span>
</template>
</template>
</a-table>
<template v-if="total > 0">
<div class="pagination-div">
<div v-if="lang" class="left">
{{ page_num }} pages / {{ total }} data
</div>
<div v-else class="left">共{{ page_num }}页/{{ total }}条数据</div>
<a-pagination
class="right"
show-quick-jumper
show-size-changer
:page-size="pageNumber"
:current="currentPage"
:page-size-options="['10', '20', '40', '100', '500']"
:total="total"
@change="changeFunction"
@showSizeChange="changeFunction"
/>
</div>
</template>
</div>
</template>
<script>
import Vue from "vue";
import VueDraggableResizable from "vue-draggable-resizable";
Vue.component("vue-draggable-resizable", VueDraggableResizable);
import EditableCell from "./EditableCell.vue";
import {countRole } from "@/api/team.js";
import optationNameObj from "./operation";
export default {
name: "TableComponent",
props: {
loading: {
type: Boolean,
default: false,
},
total: {
type: Number,
default: 10, // 数据条数
},
columns: {
type: Array,
default: () => [], // 表头
},
dataList: {
type: Array,
default: () => [], // 数据
},
operation: {
type: Array,
default: () => [], // 操作
},
hideEditableCell: {
type: Boolean,
default: false, // 是否隐藏设置表头排序按钮
},
defaultHeader: {
type: Boolean,
default: false, // false表示使用定制化表头 传true为默认表头
},
isScroll: {
type: Boolean,
default: true, // 是否使用滚动条
},
isRowSelection: {
type: Boolean,
default: false, // 是否显示checkbox
},
selectedRowKeysArray: {
type: Array,
default: () => [], // checkbox默认选中
},
defaultExpandedRowKeys: {
type: Array,
default: () => [], // 默认展开
},
// eslint-disable-next-line vue/prop-name-casing
FixedArray: {
type: Array,
default: () => [], // 固定列
},
scrollXwidth: {
type: Number,
default: 1000,
},
pageInfo: {
type: Object,
default: () => {},
},
commonTableOptationBtnInjection: {
type: String,
default: "",
},
},
data() {
this.components = {
header: {
cell: (h, props, children) => {
const { key, ...restProps } = props;
const col = this.columns.find((col) => {
const k = col.dataIndex || col.key;
return k === key;
});
if (!col || !col.width) {
return h("th", { ...restProps }, [...children]);
}
// 保存列的宽度
this.dragstopObj[col.dataIndex] = col.width;
const dragProps = {
key: col.dataIndex || col.key,
class: "table-draggable-handle",
attrs: {
w: 10,
x: col.width,
z: 1,
axis: "x",
draggable: true,
resizable: false,
},
on: {
dragging: (x) => {
const width = Math.max(x, 1);
if (width > 100) {
col.width = width;
// if (this.dragstopObj[col.dataIndex]) {
// this.scrollXwidth +=
// this.dragstopObj[col.dataIndex] - width;
// }
}
},
// dragstop: x => {
// 记录拖动结束列的宽度
// this.dragstopObj[col.dataIndex] = x;
// }
},
};
const drag = h("vue-draggable-resizable", { ...dragProps });
const obj = { ...restProps };
obj.attrs.width = col.width;
obj.class = obj.class + " " + "resize-table-th";
const draggEl = h("div", { class: "draggEl" });
let list = [...children, drag, draggEl];
if (col.dataIndex === "operation" || col.addOperation) {
if (!this.hideEditableCell) {
const str = h(EditableCell, {
attrs: {
columns: [...this.columns],
FixedArray: this.FixedArray,
},
on: {
sendData: (e) => {
this.sendDataFu(e);
},
},
});
list = [...children, str];
} else {
list = [...children];
}
}
if (col.dataIndex === "num") {
list = [...children];
}
if (
col.dataIndex === this.lastColumnsDataKey &&
this.hideEditableCell
) {
list = [...children];
}
return h("th", obj, list);
},
},
};
return {
selectedRowKeys: this.selectedRowKeysArray, // 默认选中
dragstopObj: {},
columnsData: [],
popoverIndex: null,
slotList: [],
operationObj: {},
column_name: null,
sourceData: [],
show_fields_id: null,
pageNumber: 10,
currentPage: 1,
slotObj: {},
customTitleObj: {},
// 定制化组件存储
optationNameObj: optationNameObj,
tableLoading: true,
lastColumnsDataKey: null,
lang: localStorage.getItem("lang") === "en-US",
locale: {
emptyText: <empty-text-table />,
},
// 表头图标
filterIconNameArr:[],
countRoleData:{}
};
},
computed: {
page_num() {
if (this.total % this.pageNumber > 0) {
return parseInt(this.total / this.pageNumber) + 1;
}
return this.total / this.pageNumber;
},
componentName() {
return "taskTitle";
},
},
watch: {
pageInfo: {
handler(newValue) {
this.setPageInfo(newValue);
},
deep: true,
},
columns(newValue) {
this.columnsData = newValue.filter((item) => {
return item.check;
});
},
selectedRowKeysArray(newValue) {
this.selectedRowKeys = newValue;
},
dataList(newValue) {
this.setDataList(newValue);
},
},
created() {
// 获取列表
this.column_name = this.$route.path;
this.setColumnsFu();
},
mounted() {
if (this.pageInfo) {
this.setPageInfo(this.pageInfo);
}
this.setDataList(this.dataList);
this.getcountRole()
},
methods: {
// 获取角色信息
getcountRole() {
countRole().then( res => {
if(res.errorCode == "00000"){
this.countRoleData = res.data
}
})
},
handleTableChange(pagination, filters, sorter) {
this.$emit("changeTableData", pagination, filters, sorter);
},
setDataList(newValue) {
if (newValue.length === 0) {
this.locale = { emptyText: <no-data type="2" /> };
} else {
this.locale = { emptyText: <empty-text-table /> };
}
this.sourceData = newValue.map((val, index) => {
val.op_id = JSON.stringify("_" + index);
let list = [...this.operation];
// 禁用状态下删除禁用按钮,启用状态下删除启用按钮
if (val.disaled_status) {
if (val.status === 1) {
list.splice(1, 1);
} else if (val.status === 2) {
list.splice(2, 1);
}
}
// 删除资产禁用操作按钮
if (val.delete_assets) {
list = [];
}
// 资产工单判断操作按钮,状态为待审核显示审批icon
if (val.assets_status) {
if (val.status === 1) {
list.splice(0, 1);
} else {
list.splice(1, 1);
}
}
// 消息中心草稿箱
if (val.mesPage) {
if (val.draftFlag) {
list.splice(0, 1);
} else {
list.splice(1, 1);
}
}
if (this.operation.length === 0 && val.tableOperation) {
this.$set(this.operationObj, val.op_id, val.tableOperation);
} else {
this.$set(this.operationObj, val.op_id, list);
}
return {
...val,
key: index,
num: (this.currentPage - 1) * this.pageNumber + index + 1,
};
});
this.tableLoading = false;
},
init(columns) {
if (columns.length == 0) return;
this.lastColumnsDataKey = columns[columns.length - 1].dataIndex;
this.columnsData = columns.filter((val) => {
return val.check || val.check === undefined;
});
this.columnsData.forEach((val) => {
this.slotList.push({
config: val.config || {},
dataIndex: val.dataIndex,
slots:val.slots?val.slots:{}
});
if (val.config) {
this.slotObj[val.config.slotName] = { ...val.config };
if (val.config.customTitle) {
this.customTitleObj[val.slots.title] = val.config.customTitle;
}
}
});
},
isObjEqual(o1, o2) {
// 判断两个对象是否完全相同
const props1 = Object.getOwnPropertyNames(o1);
const props2 = Object.getOwnPropertyNames(o2);
if (props1.length !== props2.length) {
return false;
}
for (let i = 0, max = props1.length; i < max; i++) {
const propName = props1[i];
if (o1[propName] !== o2[propName]) {
return false;
}
}
return true;
},
ContrastFunction(show_fields) {
const obj1 = {};
const obj2 = {};
// 只判断列名和列名绑定字段是否变化
this.columns.forEach((val) => {
obj1[val.dataIndex] = val.title;
});
show_fields.forEach((val) => {
obj2[val.dataIndex] = val.title;
});
return this.isObjEqual(obj1, obj2);
},
setColumnsFu() {
this.$emit("setColumns", this.columns);
this.init(this.columns);
},
setMultipurpose(item, record) {
if (record.status === 1) {
item.popover.contant = item.popover.contant_2;
return item.op_name_2;
} else {
item.popover.contant = item.popover.contant_1;
return item.op_name_1;
}
},
show_popover(item, index) {
this.popoverIndex = index;
item.popover.visible = true;
},
operationClick(item, record, colType) {
// debugger
let obj = Object.assign({ ...item }, { ...record }, colType)
// 操作栏的点击回调
this.$emit("getOperationData", obj);
},
onSelectChange(selectedRowKeys) {
console.log(selectedRowKeys)
this.selectedRowKeys = selectedRowKeys;
// checkbox的点击回调
this.$emit("selectedRowKeys", selectedRowKeys);
},
changeFunction(page, page_size) {
this.tableLoading = true;
// 分页的点击回调
this.pageNumber = page_size;
this.currentPage = page;
this.$emit("pagination", { page, page_size });
},
sendDataFu(e) {
// 设置表头的回调
// 保存表头数据
const list = JSON.parse(JSON.stringify([...e]));
list.forEach((val) => {
if (val.config) {
// 不保存config里面的数据
val.config = {};
}
});
const obj = {
column_name: this.column_name + this.quotaColumnName,
show_fields: JSON.stringify(list),
// show_fields: "[]" //清数据专用
};
if (this.show_fields_id) {
obj.id = this.show_fields_id;
}
// saveCustomData(obj).then((res) => {
// if (res.code === 200) {
// this.$message.success(this.$t("lang.components.table.data_save")); // 数据保存成功
// this.$emit("setColumns", e);
// }
// });
},
setPageInfo({ page_size, page }) {
if (this.total == 0) {
return;
}
if (page_size && page) {
this.pageNumber = page_size;
this.currentPage = page;
}
},
},
};
</script>
<style lang="less" scoped>
.table {
.table-icon_wrap {
display: inline-block;
margin-right: 10px;
}
/deep/ .resize-table-th {
position: relative;
.table-draggable-handle {
height: 100% !important;
bottom: 0;
left: auto !important;
right: -5px;
cursor: col-resize;
touch-action: none;
transform: none !important;
position: absolute !important;
z-index: 1 !important;
background: none !important;
}
.draggEl {
width: 3px;
height: 35%;
background: @line-gray;
position: absolute;
top: 35%;
right: 1px;
z-index: 0;
}
}
.ellipsis {
max-width: 90%;
}
.pagination-div {
width: 100%;
margin-top: 20px;
position: relative;
height: 50px;
.left {
top: 10px;
position: absolute;
left: 0;
}
.right {
position: absolute;
right: 0;
top: 0;
}
}
.option-btn {
margin-right: 0;
width: 16px;
height: 16px;
display: inline-block;
cursor: pointer;
&:hover {
color: @default-blue;
}
}
.active {
color: @default-blue;
&:hover {
cursor: pointer;
}
}
}
/deep/ .ant-table-row-cell-break-word{
vertical-align: bottom !important;
}
.filter-dropdown{
padding: 22px 16px;
width: 455px;
.ant-col{
margin-bottom: 10px;
}
.c9{
font-size: 12px;
}
}
.num-style{
color:@primary-color;
}
/deep/.ant-table-column-has-filters{
.ant-dropdown-trigger{
background: none !important;
outline: none !important;
border: none !important;
left: 50px;
width: 20px;
}
&:focus{
outline: none !important;
}
}
</style>
editableCell.vue
<template>
<a-popover
:visible="visible"
placement="bottomLeft"
overlay-class-name="table-edit-popover"
trigger="click"
>
<template slot="content">
<div class="edit-table-container">
<draggable :list="list" :animation="100" :component-data="componentData" handle=".handle">
<div v-for="(element, index) in list" :key="index">
<template v-if="element.show && !element.fixed_col">
<div class="list-group-item">
<a-checkbox
:checked="element.check"
@change="CheckboxChange(element)"
>
{{ element.title }}
</a-checkbox>
<a-icon type="menu" class="handle-btn handle" />
</div>
</template>
</div>
</draggable>
</div>
<div class="button-div">
<a-button type="primary" @click="getData">确定</a-button>
<button
class="tophant-cancel-btn btn-close"
@click="switchBtn"
>
取消
</button>
</div>
</template>
<a-icon class="a-icon-float" type="setting" @click="switchBtn" />
</a-popover>
</template>
<script>
import draggable from 'vuedraggable'
export default {
components: { draggable },
props: {
columns: {
type: Array,
default: () => [] // 表头
},
// eslint-disable-next-line vue/prop-name-casing
FixedArray: {
type: Array,
default: () => [] // 固定列
}
},
data() {
return {
list: [],
componentData: {
props: {
type: 'transition',
name: 'flip-list'
}
},
visible: false
}
},
computed: {
dragOptions() {
return {
animation: 0,
group: 'description',
disabled: false,
ghostClass: 'ghost'
}
}
},
watch: {
columns(newValue) {
this.setColumn(newValue)
}
},
created() {
this.setColumn(this.columns)
},
methods: {
setColumn(newValue) {
this.list = newValue.map((item, index) => {
return {
...item,
index: index + 1,
check: !!(item.check === true || item.check === undefined),
show: !item.fixed,
fixed_col: false
}
})
// 固定列
this.FixedArray.map(val => {
this.list[val].fixed_col = true
})
},
getData() {
this.$emit('sendData', this.list)
this.switchBtn()
},
switchBtn() {
this.visible = !this.visible
},
CheckboxChange(e) {
e.check = !e.check
}
}
}
</script>
<style scoped lang="less">
.ant-table-fixed-right .ant-table-thead > tr > th:first-child {
border-top-left-radius: 0;
z-index: 3;
}
.a-icon-float {
float: right;
position: relative;
top: 5px;
cursor: pointer;
}
.list-group-item {
width: 220px;
background-color: #ffffff;
cursor: pointer;
padding: 10px 18px;
position: relative;
user-select: none;
}
.flip-list-move {
transition: "transform 0.5s";
}
.no-move {
transition: "transform 0s";
}
.button-div {
width: 100%;
border-top: 1px solid #ededef;
padding: 18px 18px 24px 18px;
}
.btn-close {
margin-left: 10px;
}
.handle-btn {
color: #bebfc3;
position: absolute;
top: 15px;
right: 18px;
}
</style>
templete/index.js
const fileList = require.context('.', true, /\.vue$/)
const componentsObject = {}
fileList.keys().forEach(key => {
componentsObject[key.match(/(\.\/)(\w*)/)[2]] = fileList(key).default
})
export default componentsObject
opreation/index.js
// 定制操作栏按钮
const optationNameObj = {
'popover-com': () => import('./popover'), // 气泡
'drop-down': () => import('./dropDown'), // 下拉
'switchBtn': () => import('./switchBtn') // 开关
}
export default optationNameObj
opreation/dropDown/index.vue
<template>
<a-dropdown>
<span class="ant-dropdown-link" @click="e => e.preventDefault()">
<icon name="detail" />
</span>
<a-menu slot="overlay">
<a-menu-item v-for="dropItem in item.dropDown" :key="dropItem.icon">
<div @click="operationClick(item, record)">
<icon :name="dropItem.icon" />
{{ dropItem.text }}
</div>
</a-menu-item>
</a-menu>
</a-dropdown>
</template>
<script>
export default {
name: 'DropDown',
components: {},
props: {
item: {
type: Object,
default: () => {}
},
record: {
type: Object,
default: () => {}
}
},
methods: {
operationClick() {
this.$emit('operationFuClick', this.item, this.record)
}
}
}
</script>
<style></style>
opreation/popover/index.vue
<template>
<a-tooltip>
<template slot="title">
{{ item.name }}
</template>
<!-- popover组件 -->
<span>
<popoverCell
:item="item"
:record="record"
@operationClick="operationClick"
/>
</span>
</a-tooltip>
</template>
<script>
import popoverCell from './popoverCell'
export default {
name: 'PopoverCom',
components: {
popoverCell
},
props: {
item: {
type: Object,
default: () => {}
},
record: {
type: Object,
default: () => {}
}
},
methods: {
operationClick() {
this.$emit('operationFuClick', this.item, this.record)
}
}
}
</script>
<style></style>
<template>
<a-popover :visible="visible" trigger="click" placement="topRight">
<template slot="content">
<div class="popoverCell-div">
<div class="popoverCell-contant">
{{ item.popover.contant }}
</div>
<div class="popoverCell-button-div">
<a-button
class="save-btn"
type="primary"
@click="confirmClick(item, record)"
>
确定
</a-button>
<button class="tophant-cancel-btn btn-close" @click="visible = false">
取消
</button>
<div style="clear:both" />
</div>
</div>
</template>
<div class="option-btn" @click="visible = true">
<icon :name="item.icon" />
</div>
</a-popover>
</template>
<script>
export default {
props: {
item: {
type: Object,
default: () => {
return {}
}
},
record: {
type: Object,
default: () => {
return {}
}
}
},
data() {
return {
visible: false
}
},
methods: {
getPopupContainer(triggerNode) {
return triggerNode.parentNode
},
confirmClick(item, record) {
this.visible = false
this.$emit('operationClick', item, record)
}
}
}
</script>
<style lang="less">
.popoverCell-contant {
width: 230px;
}
.popoverCell-div {
width: 100%;
padding: 15px;
.popoverCell-button-div {
width: 100%;
margin-top: 20px;
.save-btn {
float: right;
}
.btn-close {
float: right;
margin-right: 10px;
}
}
}
.option-btn {
width: 16px;
height: 16px;
display: inline-block;
cursor: pointer;
&:hover {
color: @default-blue;
}
}
.ant-popover-placement-topRight > .ant-popover-content > .ant-popover-arrow {
right: 4px !important;
}
</style>
opreation/switchBtn/index.vue
<template>
<a-switch class="switch" :checked="record.switch" size="small" @click="operationClick(item, record)" />
</template>
<script>
export default {
name: 'switchBtn',
components: {},
props: {
item: {
type: Object,
default: () => {}
},
record: {
type: Object,
default: () => {}
}
},
methods: {
operationClick() {
this.$emit('operationFuClick', this.item, this.record)
}
}
}
</script>
<style scoped lang="less">
.switch {
top: -3px;
}
</style>