支持大部分场景公共表格
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/9b20dd3be33ec8300d27aa5357370a0b.png#pic_center)
HTML部分
<template>
<div
v-loading="tableLoading"
class="ky-common-table"
:element-loading-text="loadingText"
>
<el-table
ref="commonTable"
:key="updateTableKey"
border
highlight-current-row
size="mini"
header-cell-class-name="kf-common-table-header-style"
:header-cell-style="setTableHeader"
:cell-style="setCellStyle"
:cell-class-name="cellClassName"
:height="height"
:max-height="maxHeight ? maxHeight : height"
:data="tableData"
:span-method="tableCellHandle"
@sort-change="sortMethod"
@selection-change="selectTable"
>
<template
v-if="!tableData.length"
slot="empty"
>
<div class="table-empty">
<img
src="../../assets/images/empty.png"
alt="空数据占位图"
>
<div>暂无数据</div>
</div>
</template>
<el-table-column
v-if="isNeedSelection"
type="selection"
width="40"
:show-overflow-tooltip="false"
:selectable="setCheckBox"
/>
<el-table-column
v-if="isNeedIndex && tableHeaders.length"
type="index"
label="序号"
align="center"
fixed="left"
width="50"
:index="indexMethods"
:resizable="false"
/>
<template v-for="(item, index) in tableHeaders">
<k-table-column
v-if="item[moreHeaderKey] && item[moreHeaderKey].length"
:key="index"
:more-header-key="moreHeaderKey"
:item-data="item"
:stock-name="stockName"
:column-label="item.label"
v-on="$listeners"
/>
<el-table-column
v-else
:key="index"
:show-overflow-tooltip="!item.isShrink"
header-align="center"
:label="item.label"
:prop="item.prop"
:width="item.width || 'auto'"
:min-width="item.minWidth || ''"
:fixed="item.fixed || false"
:align="item.align || 'center'"
:sortable="item.sortable || false"
:resizable="false"
>
<template
v-if="item.isDefaultHearder"
slot="header"
>
<span class="default-table-span">
<span>{{ item.label }}</span>
<span v-if="item.value">{{ item.value }}</span>
</span>
<el-tooltip
v-if="item[notice]"
class="item"
effect="dark"
:content="item[notice]"
placement="top"
>
<em
class="el-icon-info"
style="margin-left: 5px;color: #2a76cd;cursor: pointer;"
/>
</el-tooltip>
</template>
<template slot-scope="{ row, column }">
<el-link
v-if="item.canCreateURL"
type="primary"
>
{{ row[item.prop] }}
</el-link>
<span v-else-if="item.isThousands">{{ row[item.prop] | moneyThousands }}</span>
<div
v-else-if="item.isShrink"
class="shrink-area"
>
<template v-if="row[column.property].length <= 10">
<template v-for="val in row[stockName]">
<span
:key="val.code"
class="stock-item"
>{{ val.name }}</span>
</template>
</template>
<template v-if="row[column.property].length > 10">
<template v-for="(val, idx) in row[stockName]">
<span
v-show="idx < 9"
:key="idx"
class="stock-item"
>{{ val.name }}</span>
</template>
<el-popover
trigger="hover"
placement="right"
popper-class="popover-default-style"
class="default-popover"
>
<div class="list-area">
<span
v-for="val in row[stockName].slice(9)"
:key="val.code"
class="popover-item"
>{{ val.name }}</span>
</div>
<el-button
slot="reference"
class="shrink"
size="mini"
>
+{{ row[stockName].length - 9 }}
</el-button>
</el-popover>
</template>
</div>
<span
v-else-if="!item.stopStyle && row[item.prop] && row[item.prop].toString().includes('%') && Number(row[item.prop].slice(0, row[item.prop].length - 1))"
:style="{ color: row[item.prop].toString().includes('-') ? '#388B0A' : '#C1362C' }"
>{{ row[item.prop] }}</span>
<span v-else>{{ row[item.prop] }}</span>
</template>
</el-table-column>
</template>
<el-table-column
v-if="isNeedControl"
fixed="right"
label="操作"
align="center"
:width="width"
:resizable="false"
>
<template slot-scope="scope">
<template v-if="controlType === 'combination'">
<el-button
v-if="isNeedDetail"
type="text"
style="font-size: 14px"
@click="watchDetail(scope)"
>
详情
</el-button>
<el-button
v-if="isNeedCopy"
type="text"
style="font-size: 14px"
@click="copyData(scope)"
>
复制
</el-button>
<el-button
v-if="isNeedAdustment"
type="text"
style="font-size: 14px"
@click="adjustmentData(scope)"
>
调仓
</el-button>
<el-popover placement="bottom-end">
<p style="font-size: 16px;color: #3C4455;font-weight: 700;">
{{ promptTitle }}
</p>
<p style="font-size: 14px;color: #3C4455;margin: 4px 0 12px">
{{ promptDescription }}
</p>
<div style="text-align: right; margin: 0">
<el-button
size="mini"
class="cancel"
style="font-size: 14px"
@click="cancelDeletion"
>
取消
</el-button>
<el-button
type="primary"
size="mini"
style="background: #2A76CD;border-color: #2A76CD"
@click="deleteData(scope)"
>
确定
</el-button>
</div>
<el-button
v-if="isNeedDelete && isNeedWindowDelete !== true"
slot="reference"
type="text"
style="margin: 0 8px;font-size: 14px"
>
删除
</el-button>
</el-popover>
<el-button
v-if="isNeedStop"
type="text"
:disabled="!scope.row.isShowStop"
:class="{showGray: !scope.row.isShowStop}"
style="font-size: 14px"
@click="stopData(scope)"
>
停用
</el-button>
<el-button
v-if="isNeedEnable"
type="text"
:disabled="!scope.row.isShowStart"
:class="{showGray: !scope.row.isShowStart}"
style="font-size: 14px"
@click="enableData(scope)"
>
启用
</el-button>
</template>
<template v-if="controlType === 'researcher'">
<el-button
v-if="isNeedDetail"
type="text"
style="font-size: 14px"
@click="watchDetail(scope)"
>
详情
</el-button>
</template>
<template v-if="controlType === 'stockRecommend'">
<el-button
v-if="isNeedSee"
type="text"
style="font-size: 14px"
@click="seeData(scope)"
>
查看
</el-button>
<el-button
v-if="isNeedModify"
type="text"
style="font-size: 14px"
:disabled="scope.row.displayType === '1'"
:class="{showGray: scope.row.displayType === '1'}"
@click="modifyData(scope)"
>
修改
</el-button>
<el-popover placement="bottom-end">
<p style="font-size: 16px;color: #3C4455;font-weight: 700;">
{{ promptTitle }}
</p>
<p style="font-size: 14px;color: #3C4455;margin: 4px 0 12px">
{{ promptDescription }}
</p>
<div style="text-align: right; margin: 0">
<el-button
size="mini"
class="cancel"
style="font-size: 14px"
@click="cancelDeletion"
>
取消
</el-button>
<el-button
type="primary"
size="mini"
style="background: #2A76CD;border-color: #2A76CD;font-size: 14px"
@click="deleteData(scope)"
>
确定
</el-button>
</div>
<el-button
v-if="isNeedWindowDelete && isNeedDelete !== true"
slot="reference"
type="text"
style="margin: 0 8px;font-size: 14px"
:disabled="scope.row.displayType === '1'"
:class="{showGray: scope.row.displayType === '1'}"
>
删除
</el-button>
</el-popover>
<el-button
v-if="isNeedSubmit"
type="text"
style="font-size: 14px"
:disabled="scope.row.displayType === '1'"
:class="{showGray: scope.row.displayType === '1'}"
@click="submitData(scope)"
>
提交
</el-button>
</template>
<template v-if="controlType === 'industryRecommend'">
<el-button
v-if="isNeedDetail"
type="text"
style="font-size: 14px"
@click="watchDetail(scope)"
>
详情
</el-button>
<el-button
v-if="isNeedCopy"
type="text"
style="font-size: 14px"
@click="copyData(scope)"
>
复制
</el-button>
<el-button
v-if="isNeedAdustment"
type="text"
:class="{showGray: !scope.row.isShowStop}"
:disabled="!scope.row.isShowStop"
style="font-size: 14px"
@click="adjustmentData(scope)"
>
调仓
</el-button>
<el-popover placement="bottom-end">
<p style="font-size: 16px;color: #3C4455;font-weight: 700;">
{{ promptTitle }}
</p>
<p style="font-size: 14px;color: #3C4455;margin: 4px 0 12px">
{{ promptDescription }}
</p>
<div style="text-align: right; margin: 0">
<el-button
size="mini"
class="cancel"
@click="cancelDeletion"
>
取消
</el-button>
<el-button
type="primary"
size="mini"
style="background: #2A76CD;border-color: #2A76CD;"
@click="deleteData(scope)"
>
确定
</el-button>
</div>
<el-button
v-if="isNeedDelete && isNeedWindowDelete !== true"
slot="reference"
type="text"
style="margin: 0 8px;font-size: 14px"
>
删除
</el-button>
</el-popover>
<el-button
v-if="isNeedStop"
type="text"
:disabled="!scope.row.isShowStop"
:class="{showGray: !scope.row.isShowStop}"
style="font-size: 14px"
@click="stopData(scope)"
>
停用
</el-button>
<el-button
v-if="isNeedEnable"
type="text"
:disabled="!scope.row.isShowStart"
:class="{showGray: !scope.row.isShowStart}"
style="font-size: 14px"
@click="enableData(scope)"
>
启用
</el-button>
</template>
<template v-if="controlType === 'stockIndustryCombination'">
<el-button
v-if="isNeedDetail"
type="text"
style="font-size: 14px"
@click="watchDetail(scope)"
>
详情
</el-button>
</template>
<template v-if="controlType === 'stockGoldCombination'">
<el-button
v-if="isNeedDetail"
type="text"
style="font-size: 14px"
@click="watchDetail(scope)"
>
详情
</el-button>
</template>
</template>
</el-table-column>
</el-table>
<el-pagination
v-show="tableData.length && isShowPagination"
small
background
popper-class="page-paging"
layout="total, sizes, prev, pager, next, jumper"
:page-sizes="pageSizes"
:page-size="pageSize"
:total="totalSize"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</template>
js部分
<script>
import KTableColumn from "@/components/KCommonTable/KTableColumn";
import { numberFilter } from "@/utils/validate";
import { mapGetters } from "vuex";
import store from '../../store'
export default {
name: 'KCommonTable',
components: {
KTableColumn,
},
filters: {
moneyThousands (value) {
let result = ''
result = Number(value) ? numberFilter(value) : value
return result
},
},
props: {
showFirstIndex: {
type: Boolean,
default: true,
},
height: {
type: Number,
default: 487,
},
width: {
type: String,
default: "100px",
},
maxHeight: {
type: Number,
},
isNeedIndex: {
type: Boolean,
default: false,
},
isNeedSelection: {
type: Boolean,
default: false,
},
tableHeaders: {
type: Array,
default() {
return [];
},
},
tableData: {
type: Array,
default() {
return [];
},
},
notice: {
type: String,
default: "notice",
},
stockName: {
type: String,
default: "stockName",
},
tableLoading: {
type: Boolean,
default: false,
},
loadingText: {
type: String,
default: "加载中...",
},
isNeedControl: {
type: Boolean,
default: true,
},
isNeedDetail: {
type: Boolean,
default: false,
},
isNeedCopy: {
type: Boolean,
default: false,
},
isNeedAdustment: {
type: Boolean,
default: false,
},
isNeedStop: {
type: Boolean,
default: false,
},
isNeedSee: {
type: Boolean,
default: false,
},
isNeedModify: {
type: Boolean,
default: false,
},
isNeedDelete: {
type: Boolean,
default: false,
},
isNeedWindowDelete: {
type: Boolean,
default: false,
},
isNeedEnable: {
type: Boolean,
default: false,
},
isNeedSubmit: {
type: Boolean,
default: false,
},
isNeedMergeTable: {
type: Boolean,
default: false,
},
mergeSpanRow: {
type: Array,
default() {
return []
},
},
totalSize: {
type: Number,
default: 0,
},
mergeColumn: {
type: String,
default: "",
},
pageSizes: {
type: Array,
default() {
return [10, 20, 30, 40];
},
},
pageSize: {
type: Number,
default: 10,
},
currentPageNums: {
type: Number,
default: 1,
},
isShowPagination: {
type: Boolean,
default: true,
},
threeLayerPrompt: {
type: Boolean,
default: false,
},
promptTitle: {
type: String,
default: "请确认是否删除组合?",
},
promptDescription: {
type: String,
default: "删除后将无法再看到组合信息及组合持仓。",
},
checkBoxArr: {
type: Array,
default() {
return [];
},
},
typeId: {
type: Number,
default: 0,
},
controlType: {
type: String,
default: "",
},
moreHeaderKey: {
type: String,
default: "children",
},
},
data() {
return {
setTableHeader: {
backgroundColor: "#f3f4f6",
fontSize: "14px",
color: "#3C4455",
textAlign: "center",
lineHeight: "20px",
fontweight: 700,
height: "40px",
padding: "0",
},
userName: '',
updateTableKey: Math.random(),
};
},
updated() {
if (this.$refs.commonTable && this.$refs.commonTable.doLayout) {
this.$refs.commonTable.doLayout();
}
},
methods: {
indexMethods(index) {
let result = "";
if (index !== 0 && !this.showFirstIndex) {
result = (this.currentPageNums - 1) * this.pageSize + index;
} else if (this.showFirstIndex) {
result = (this.currentPageNums - 1) * this.pageSize + index + 1;
}
return result;
},
setCellStyle({
row,
rowIndex,
}) {
let resultClass = {};
let isTotal = false;
isTotal = Object.values(row)
.includes("合计");
resultClass = {
color: "#3C4455",
};
if (isTotal && rowIndex === 0) {
resultClass = { color: "#2a76cd" };
}
resultClass.height = "34px";
resultClass.padding = "0";
resultClass.fontSize = "14px";
return resultClass;
},
cellClassName({
row,
column,
}) {
const value = row[column.property];
const label = column.property;
let cellClassName = "";
if (label && label === "IS_ENABLE" && value && (value.includes("启用") || value.includes("停用"))) {
cellClassName = value.includes("启用") ? "enable" : "deactivate";
}
if (label && label === "status" && value && (value.includes("未提交") || value.includes("已提交"))) {
cellClassName = value.includes("未提交") ? "not-submitted" : "submitted";
}
return cellClassName;
},
watchDetail({ row }) {
this.$emit("watchDetail", row);
},
copyData({ row }) {
this.$emit("copyData", row);
},
adjustmentData({ row }) {
this.$emit("adjustmentData", row);
},
seeData({ row }) {
this.$emit("seeData", row);
},
modifyData({ row }) {
this.$emit("modifyData", row);
},
deleteData({ row }) {
this.$emit("deleteData", row);
document.body.click();
},
stopData({ row }) {
this.$emit("stopData", row);
},
enableData({ row }) {
this.$emit("enableData", row);
},
submitData({ row }) {
this.$emit("submitData", row);
},
handleSizeChange(val) {
this.$emit("handleSizeChange", val);
},
handleCurrentChange(val) {
this.$emit("handleCurrentChange", val);
},
resetHandle() {
this.$refs.commonTable.doLayout();
},
sortMethod(sortValue) {
this.$emit("sortMethods", sortValue);
},
cancelDeletion() {
document.body.click();
},
setCheckBox(row) {
return !this.checkBoxArr.includes(row.stockCode) && row.isAsh === "0";
},
selectTable(data) {
const selectArr = data.map(item => item.stockCode);
this.$emit("selectChange", selectArr);
},
tableCellHandle({
rowIndex,
column,
}) {
if (this.isNeedMergeTable) {
if (column.property === this.mergeColumn) {
const result = {
rowspan: 0,
colspan: 0,
};
if (this.mergeSpanRow[rowIndex]) {
result.rowspan = this.mergeSpanRow[rowIndex];
result.colspan = 1;
}
return result;
}
}
},
updateTableData() {
this.updateTableKey = Math.random();
},
},
computed: {
...mapGetters(['userIdentity']),
},
mounted() {
this.userName = store.state.user.userInfo.name || ''
},
};
</script>
scss
<style scoped lang="scss">
.ky-common-table {
width: 100%;
.shrink-area {
display: flex;
align-items: center;
flex-wrap: wrap;
.stock-item {
display: inline-block;
text-align: center;
width: 20%;
padding: 2px 3px;
}
span:last-child {
display: inline-block;
width: 20%;
}
.el-button {
margin-bottom: 8px;
width: 50px;
height: 20px;
background: #E8F7FF;
border-radius: 3px;
color: #2A76CD;
border: none;
line-height: 20px;
padding: 0;
}
}
.el-pagination {
margin: 14px 0;
}
}
</style>
<style lang="scss">
.ky-common-table {
.el-button--small {
color: #2A76CD;
&:focus,
&:hover {
border-color: transparent;
background-color: transparent;
}
}
.el-table__cell {
padding: 4px 0;
&:last-child {
padding: 0;
}
}
.el-button--small.is-disabled.showGray {
color: rgb(192, 196, 204)
}
.commonStyle {
display: inline-block;
width: 46px;
height: 25px;
line-height: 25px;
font-size: 14px;
box-sizing: border-box;
border-radius: 50px;
}
.enable {
span {
@extend .commonStyle;
border: 1px solid #1199AD;
color: #1199AD;
background: rgb(226, 243, 245);
}
}
.deactivate {
span {
@extend .commonStyle;
border: 1px solid #D69C15;
color: #D69C15;
background: rgb(250, 243, 227);
}
}
.commonSubmitStyle {
display: inline-block;
width: 52px;
height: 24px;
line-height: 24px;
font-size: 14px;
text-align: center;
box-sizing: border-box;
border-radius: 3px;
}
.not-submitted {
span {
@extend .commonSubmitStyle;
background: #EAEBEB;
color: #3C4455;
}
}
.submitted {
span {
@extend .commonSubmitStyle;
background: #E8F7FF;
color: #2A76CD;
}
}
tbody {
td {
.cell {
width: 100% !important;
}
}
}
}
.industry-combination,
.broker-gold-money,
.researcherPanorama {
el-table__header {
th.el-table__cell {
padding: 6px 0;
}
}
}
.el-popper {
.el-button--mini.cancel {
&:focus,
&:hover {
background: #FFFFFF;
border-color: #2A76CD;
color: #2A76CD;
}
}
}
.deactivate-combination,
.el-popover.el-popper {
.el-button--mini {
width: 44px;
height: 22px;
padding: 0;
}
}
.compositePanorama,
.researcherPanorama {
.composite-panorama {
margin-top: 24px;
.el-table {
.el-table__header-wrapper,
.el-table__body-wrapper {
.el-table__cell {
padding: 0;
}
}
}
}
}
.el-table__body tr.current-row > td {
background: rgb(245, 247, 250) !important;
}
// 解决表头错位问题
.el-table th.gutter {
display: table-cell !important;
}
.popover-default-style {
width: 200px;
.list-area {
display: flex;
align-items: center;
flex-wrap: wrap;
.popover-item {
display: inline-block;
width: 50%;
text-align: center;
}
}
}
.combinationGlance-content {
.gold-share-list {
.el-table__header-wrapper {
.cell {
display: flex !important;
justify-content: center;
align-items: center;
.default-table-span {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
}
}
}
}
</style>
子表格
<template>
<el-table-column class="ky-table-column" :label="columnLabel">
<template v-for="(item, index) in itemData[moreHeaderKey]">
<template v-if="item[moreHeaderKey] && item[moreHeaderKey].length">
<KTableColumn
:key="index"
:more-header-key="moreHeaderKey"
:item-data="item"
:stock-name="stockName"
:column-label="item.label"
/>
</template>
<el-table-column
v-else
:key="index"
:label="item.label"
:width="item.width || 'auto'"
:fixed="item.fixed || false"
:align="item.align || 'center'"
>
<template v-if="item.isDefaultHearder" slot="header">
<span>{{ item.label }}</span>
<el-tooltip class="item" effect="dark" :content="itemData[notice]" placement="top">
<em class="el-icon-info" style="margin-left: 5px;color: #2a76cd;cursor: pointer;"></em>
</el-tooltip>
</template>
<template slot-scope="{ row, column }">
<el-link v-if="item.canCreateURL && row[item.prop] !== '--' && row[item.prop] !== ''" type="primary"
@click="clickUrl(row,item)">
{{
row[item.prop]
}}
</el-link>
<span v-else-if="item.isThousands">{{ row[item.prop] | moneyThousands }}</span>
<div v-else-if="item.isShrink" class="shrink-area">
<template v-if="row[column.property].length <= 10">
<template v-for="val in row[stockName]">
<span class="stock-item" :key="val.code">{{ val.name }}</span>
</template>
</template>
<template v-if="row[column.property].length > 10">
<template v-for="(val, idx) in row[stockName]">
<span v-show="idx < 9" class="stock-item" :key="idx">{{ val.name }}</span>
</template>
<el-popover
trigger="hover"
placement="right"
popper-class="popover-default-style"
>
<div class="list-area">
<span v-for="val in row[stockName].slice(9)" :key="val.code" class="popover-item">{{
val.name
}}</span>
</div>
<el-button class="shrink" slot="reference" size="mini">+{{ row[stockName].length - 9 }}</el-button>
</el-popover>
</template>
</div>
<span v-else :class="judgeColor(row, item)">{{ row[item.prop] }}</span>
</template>
</el-table-column>
</template>
</el-table-column>
</template>
js部分
<script>
import { numberFilter } from '@/utils/validate'
export default {
name: 'KTableColumn',
props: {
itemData: {
type: Object,
default () {
return {}
},
},
notice: {
type: String,
default: 'notice',
},
stockName: {
type: String,
default: 'stockName',
},
moreHeaderKey: {
type: String,
default: 'children',
},
columnLabel: {
type: String,
default: '',
},
},
methods: {
clickUrl (row, item) {
this.$emit('clickUrl', row, item)
},
judgeColor (row, item) {
if (row[item.prop].includes('%') && Number(row[item.prop].slice(0, row[item.prop].length - 1))) {
if (!row[item.prop].includes('-')) {
return 'upStyle'
}
return 'downStyle'
}
return ''
},
},
filters: {
moneyThousands (value) {
let result = ''
result = Number(value) ? numberFilter(value) : value
return result
},
},
}
</script>
scss
<style lang="scss">
.upStyle {
color: #C1362C;
}
.downStyle {
color: #388B0A;
}
</style>
numberFilter
export function numberFilter (data) {
let dataValue = ''
if ((typeof data === 'string' || typeof data === 'number') && Number(data)) {
dataValue = Number(data)
}
if (data && Number(dataValue)) {
const newNum = dataValue.toLocaleString('en-US', { minimumFractionDigits: 2 })
return newNum
}
}