<template> <div class="header-actions" v-if="getActionList.length != 0 &&getActionList.length>2"> <div class="overflow-box"> <el-button v-for="(item, index) in getActionList" :key="index" :type="item.type || 'primary'" :plain="item.plain || false" v-bind="getHeaderActions(item)" @click="item.action" :disabled="item.disabled || false"> {{ item.text }} </el-button> </div> </div> <div class="stat-warp" v-if="statConfig.length != 0" :class="statWarpHeight > 200 && isMore ? 'show-more' : ''"> <div class="title"> <select v-model="statSelectVal" v-if="statConfig.length > 1" @change="changeStatData"> <option :value="index" v-for="(i, index) in statConfig" :key="index"> {{ i.label }} </option> </select> <div v-if="statConfig.length === 1">{{ statConfig[0].label }}</div> </div> <div class="more-btn" @click="isMore = !isMore" v-if="statWarpHeight > 200"> <span> <i v-if="!isMore" class="iconfont icon-btn_xiala22"></i> <i v-else class="iconfont icon-btn_shouqi22"></i> </span> </div> <ul id="statWarp"> <li v-show="!i.data" :class="'theme' + i.type" v-for="(i, index) in statConfig[statSelectVal].data" :key="index" @click="i.click ? i.click(i, index) : ''" :style="i.click ? 'cursor: pointer' : ''"> <div class="label">{{ i.label }}</div> <div class="num">{{ i.num }}</div> </li> <li v-show="i.data" v-for="(i, index) in statConfig[statSelectVal].data" :key="index" class="multi-data" :class="'theme' + i.type" @click="i.click ? i.click(i, index) : ''" :style="i.click ? 'cursor: pointer' : ''"> <div class="label">{{ i.label }}</div> <div class="num-warp"> <div class="num-box" v-for="(j, jindex) in i.data" :key="jindex"> <div class="num-small" :style="'color:' + j.color"> {{ j.num }} </div> <div class="label-small">{{ j.label }}</div> </div> </div> </li> </ul> </div> <div class="table-list-container by-table" v-loading="loading" v-if="!hideAll"> <!-- v-if="!hideHeader" --> <header v-if="false" class="header"> <h2>{{ title }}</h2> </header> <div class="by-search" v-if="!hideSearch"> <div style="display: flex"> <div v-if="getActionList.length != 0 &&getActionList.length <=2" style="margin-right:10px"> <el-button v-for="(item, index) in getActionList" :key="index" :type="item.type || 'primary'" :plain="item.plain || false" v-bind="getHeaderActions(item)" @click="item.action" :disabled="item.disabled || false"> {{ item.text }} </el-button> </div> <div class="by-dropdown" v-for="(i, index) in selectConfigCopy" :key="i.prop" style="margin-right: 10px"> <div v-if="i.type" class="selectTime" style="display:flex;align-items:center"> <span style="font-size:14px;height:32px;margin-right:15px">{{i.label || ''}}</span> <el-date-picker v-model="pagination[i.prop]" type="date" size="small" :placeholder="i.placeholder" style="width:120px" value-format="YYYY-MM-DD" @change="searchItemSelctOne(i,pagination[i.prop],pagination[i.propOne])" /> <span style="margin-right:15px">-</span> <el-date-picker v-model="pagination[i.propOne]" type="date" size="small" :placeholder="i.placeholderOne" style="width:120px" value-format="YYYY-MM-DD" @change="searchItemSelctOne(i,pagination[i.prop],pagination[i.propOne])" /> </div> <div v-else> <div class="by-dropdown-title"> <!-- {{i.label || i.labelCopy}} --> {{ pagination[i.prop] ? i.data.find((j) => j.value === pagination[i.prop]) ? i.data.find((j) => j.value === pagination[i.prop]).label : i.label : i.labelCopy }} <i style="margin-left: 5px" class="iconfont icon-iconm_xialan1"></i> </div> <ul class="by-dropdown-lists"> <li @click="searchItemSelct('all', i, index)" v-if="i.isShowAll === false ? i.isShowAll : true" style=""> {{ $t("common.all") }} </li> <li v-for="j in i.data" :key="j.value || j.dictKey" @click="searchItemSelct(j, i)" style=""> {{ j.label || j.dictValue }} </li> </ul> </div> </div> </div> <div style="display: flex"> <el-input :placeholder="$t('common.pleaseEnterKeywords')" suffix-icon="search" size="mini" v-model="pagination.keyword" @keyup.enter="searchFn"> </el-input> <el-button type="primary" style="margin-left: 10px" size="default" @click="searchFn">{{ $t("common.search") }}</el-button> <div class="more-icon" @click="retrievalModalFn"> <img src="@/assets/images/iconm_xiangyzk.png" alt="" /> </div> </div> </div> <component :is="containerTag"> <div class="filter-form-container"> <slot /> </div> <el-table ref="hocElTable" :data="source" v-if="!hideTable" style="width: 100%" v-bind="$attrs" v-on="tableEvents" row-key="id" :tree-props="{ children: 'children', hasChildren: 'hasChildren', }" :height="tableHeight"> <el-table-column v-for="(item, index) in configData" :key="index" v-bind="getAttrsValue(item)" :type="item.type || ''" :selectable=" (rowData, rowIndex) => isSelectable(rowData, rowIndex, item) "> <template #header v-if="item.attrs.isNeedHeaderSlot"> <slot :name="item.attrs.headerSlot" :headerLabel="item.attrs.label" v-if="item.attrs.headerSlot"> 头部插槽占位符 </slot> </template> <template #default="scope" v-if="!item.type"> <slot :name="item.attrs.slot" :item="scope.row" :headerLabel="item.attrs.label" v-if="item.attrs.slot"> 插槽占位符 </slot> <div v-else-if="isFunction(getValue(scope, item))"> <component :is="renderTypeList[getMatchRenderFunction(item)].target" :cell-list="getValue(scope, item)()" :row="scope.row" :parent="getParent" @click=" ($event) => { handleNativeClick(getAttrsValue(item), $event, item); } " /> </div> <div v-else> {{ getValue(scope, item) }} </div> </template> </el-table-column> </el-table> <el-row v-if="!hidePagination" class="table-pagination" justify="end" type="flex"> <el-pagination background layout="total, sizes, prev, pager, next, jumper" :current-page="getPagination.pageNum" :page-size="getPagination.pageSize" :total="getPagination.total" @size-change="handleSizeChange" @current-change="handlePageChange" /> </el-row> </component> </div> </template> <script> import { isFunction as isFn, isBoolean } from "./type"; import ElementsMapping from "./ElementsMapping"; import ComponentsMapping from "./ComponentsMapping"; import { computed, defineComponent, getCurrentInstance, ref, watch } from "vue"; import expand from "./expand"; import Sortable from "sortablejs"; export default defineComponent({ name: "Table", components: { ElementsMapping, ComponentsMapping, }, props: { hideSearch: { type: Boolean, default: false, }, onMoreSearch: { type: Boolean, default: true, }, hideAll: { type: Boolean, default: false, }, hideTable: { type: Boolean, default: false, }, //顶部搜索下拉配置 selectConfig: { type: Array, default() { return []; }, }, // 获取表格元数据时携带的参数 filterParams: { type: Object, default() { return {}; }, }, // 表格加载 loading loading: { type: Boolean, default: false, }, // 表格名称 title: { type: String, default: "", }, // 表格元数据 source: { type: Array, required: true, default() { return []; }, }, tableHeight: { type: Number, required: false, }, searchConfig: { type: Object, default() { return { keyword: "", }; }, }, statConfig: { type: Array, default() { return []; }, }, // 指定外层容器的渲染组件 containerTag: { type: String, default: "div", }, // 是否隐藏表头 hideHeader: { type: Boolean, default: false, }, // 是否隐藏分页 hidePagination: { type: Boolean, default: false, }, // 分页配置 pagination: { type: Object, default() { return {}; }, }, // 表格配置文件 config: { type: Array, default() { return []; }, }, // 表头右上方的按钮组 actionList: { type: Array, default() { return [{ text: "", action: () => {} }]; }, }, // element table 原生事件 tableEvents: { type: Object, default() { return {}; }, }, searchKey: { type: String, default: "keyword", }, // 是否显示过滤的全部选项 // isShowAll: { // type: Boolean, // default: true, // }, }, setup(props) { const { proxy } = getCurrentInstance(); // 过滤出有属性的 // const configData = ref([]); // configData.value = proxy.config.filter((x) => x && x.attrs); const configData = computed(() => proxy.config.filter((x) => x && x.attrs)); // console.log(configData.value, "ssssssssss"); const selectConfigCopy = computed(() => { return props.selectConfig.map((item) => { if (!item.labelCopy) item.labelCopy = { ...item }.label; return item; }); }); let isMore = ref(false); const changeStatData = () => { statWarpHeight.value = document.getElementById("statWarp").offsetHeight; }; let statWarpHeight = ref(0); watch( proxy.statConfig, (newValue, oldValue) => { setTimeout(() => { //获取statWarp的height statWarpHeight.value = document.getElementById("statWarp").offsetHeight; }, 500); }, { immediate: true } ); let statSelectVal = ref(0); const retrievalModal = ref(false); const getAttrsValue = (item) => { const { attrs } = item; const result = { ...attrs, }; delete result.prop; return result; }; const renderTypeList = ref({ render: {}, renderHTML: { target: "elements-mapping", }, renderComponent: { target: "components-mapping", }, renderMoreBtn: { target: "more-btn", }, }); const getParent = computed(() => { return proxy.$parent; }); const getPagination = computed(() => { const params = { pageNum: 1, pageSize: 10, total: 0, }; return Object.assign({}, params, props.pagination); }); const getActionList = computed(() => { return props.actionList .slice() .reverse() .filter((it) => it.text); }); const getValue = (scope, configItem) => { const prop = configItem.attrs.prop; const renderName = getMatchRenderFunction(configItem); const renderObj = renderTypeList.value[renderName]; if (renderObj && isFunction(configItem[renderName])) { return renderObj.target ? getRenderValue(scope, configItem, { name: renderName, type: "bind", }) : getRenderValue(scope, configItem); } return scope.row[prop]; }; const getRenderValue = ( scope, item, fn = { name: "render", type: "call" } ) => { const prop = item.attrs.prop; const propValue = prop && scope.row[prop]; scope.row.$index = scope.$index; const args = propValue !== undefined ? propValue : scope.row; return item[fn.name][fn.type](getParent.value, args); }; // 匹配 render 开头的函数 const getMatchRenderFunction = (obj) => { return Object.keys(obj).find((key) => { const matchRender = key.match(/^render.*/); return matchRender && matchRender[0]; }); }; const isFunction = (fn) => { return isFn(fn); }; const searchFn = (val) => { if (props.loading) return; proxy.$emit( "getList", Object.assign(props.filterParams, { [props.searchKey]: props.pagination.keyword, }) ); }; const retrievalModalFn = () => { proxy.$emit("moreSearch", ""); //获取父组件定义的moreSearch方法 }; const handlePageChange = (val) => { proxy.$emit( "getList", Object.assign(props.filterParams, { pageNum: val }) ); }; const handleSizeChange = (val) => { proxy.$emit( "getList", Object.assign(props.filterParams, { pageSize: val }) ); }; const getHeaderActions = (item) => { return { ...item.attrs, }; }; const stopBubbles = (e) => { const event = e || window.event; if (event && event.stopPropagation) { event.stopPropagation(); } else { event.cancelBubble = true; } }; const handleNativeClick = ({ isBubble }, e, item) => { // 考虑到单元格内渲染了组件,并且组件自身可能含有点击事件,故添加了阻止冒泡机制 // 若指定 isBubble 为 false,则当前单元格恢复冒泡机制 if (isBoolean(isBubble) && !isBubble) return; stopBubbles(e); }; //下拉搜索相关 const searchItemSelct = (item, i, index = 0) => { if (item == "all") { i.label = { ...props.selectConfig[index] }.labelCopy; i.label = props.selectConfig[index].labelCopy; proxy.$emit( "getList", Object.assign(props.filterParams, { [i.prop]: "" }) ); return; } i.label = item.label || item.dictValue; proxy.$emit( "getList", Object.assign(props.filterParams, { [i.prop]: item.value || item.dictKey, }) ); }; const searchItemSelctOne = (item, prop, propOne) => { if (prop && propOne) { proxy.$emit( "getList", Object.assign(props.filterParams, { [item.prop]: prop, [item.propOne]: propOne, }) ); } else if (prop == null && propOne == null) { proxy.$emit( "getList", Object.assign(props.filterParams, { [item.prop]: prop, [item.propOne]: propOne, }) ); } }; const isSelectable = (row, index, item) => { if (item.type === "selection") { if (item.attrs && item.attrs.checkAtt) { if (row[item.attrs.checkAtt]) { return row[item.attrs.checkAtt]; } } else { return true; } } }; const hocElTable = ref(); return { configData, getParent, getPagination, renderTypeList, getActionList, getAttrsValue, getValue, getRenderValue, getMatchRenderFunction, isFunction, handlePageChange, handleSizeChange, getHeaderActions, stopBubbles, handleNativeClick, searchFn, searchItemSelct, searchItemSelctOne, selectConfigCopy, isSelectable, retrievalModal, retrievalModalFn, statSelectVal, statWarpHeight, isMore, changeStatData, hocElTable, }; }, }); </script> <style> .table-list-container th { color: #333 !important; } .by-table td .el-button + .el-button { margin-left: 0 !important; } .by-table td .el-button { background: none !important; margin: 0 !important; padding: 8px 6px !important; } .el-checkbox__input.is-disabled .el-checkbox__inner { background-color: #dee1e6; border-color: #b2b4b9; } .el-table .cell { line-height: 34px; } </style> <style lang="scss" scoped> .sortableActive { background: #f5f7fa !important; } .show-more { height: auto !important; } .stat-warp { margin-bottom: 20px; background: #fff; padding: 0 20px; height: 200px; overflow: hidden; position: relative; .more-btn { position: absolute; right: 0; bottom: 0; left: 0; height: 40px; cursor: pointer; font-size: 12px; line-height: 30px; text-align: center; background: linear-gradient( 180deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.8) 100% ); i { color: #999; } } .title { height: 60px; select { height: 60px; border: none; outline: none; -webkit-appearance: none; appearance: none; font-size: 14px; font-weight: bold; background: url("@/assets/images/sanjiao.png") no-repeat right center; padding-right: 20px; } div { height: 60px; font-size: 14px; font-weight: bold; line-height: 60px; } } ul { padding: 0; overflow: hidden; margin: 0; li { list-style: none; min-width: 285px; box-sizing: border-box; margin: 0 20px 20px 0; background: #eff6ff; float: left; overflow: hidden; padding: 20px; color: #333333; border-radius: 10px; .label { font-size: 14px; } .label::before { width: 10px; height: 10px; content: ""; border-radius: 50%; background: #0084ff; display: inline-block; margin-right: 10px; } .num { margin-top: 10px; font-size: 24px; font-weight: bold; } } //#F5F3FF #9E64ED .theme2 { background: #f5f3ff; .label::before { background: #9e64ed; } } //#FFF1E1 #FF9315 .theme3 { background: #fff1e1; .label::before { background: #ff9315; } } //#E2FBE8 #39C55A .theme4 { background: #e2fbe8; .label::before { background: #39c55a; } } .theme5 { background: #ffebe9; .label::before { background: #f94539; } } .theme6 { background: #e4f9f9; .label::before { background: #53cbcb; } } .multi-data { .label::before { display: none; } .label { font-size: 14px; font-weight: bold; color: #333; margin-bottom: 8px; } .num-warp { overflow: hidden; .num-box { float: left; min-width: 80px; margin-right: 20px; .num-small { font-size: 16px; font-weight: bold; margin-bottom: 8px; } .label-small { color: #666; font-size: 14px; } } } } } } .by-search { display: flex; justify-content: space-between; margin-bottom: 10px; .more-icon { float: right; cursor: pointer; // line-height: 32px; text-align: center; margin-left: 5px; } } .by-dropdown { position: relative; text-align: left; height: 32px; z-index: 1010; padding: 0 10px; transition: all 0.5s ease; cursor: pointer; line-height: 32px; .by-dropdown-title { font-size: 14px; background-color: #fff; } ul { position: absolute; left: 0; top: 32px; padding: 0; margin: 0; z-index: 100; display: none; white-space: nowrap; min-width: 80px; li { list-style: none; font-size: 12px; height: 30px; padding: 0 10px; text-align: left; line-height: 30px; } li:hover { background-color: #eff6ff; color: #0084ff; } } } .by-dropdown::before { display: block; width: 1px; content: " "; position: absolute; height: 14px; top: 8px; background-color: #ddd; right: 0; z-index: 1011; } .by-dropdown:hover { background: #ffffff; border-radius: 2px 2px 2px 2px; opacity: 1; ul { background: #ffffff; box-shadow: 0px 2px 16px 1px rgba(0, 0, 0, 0.06); border-radius: 2px 2px 2px 2px; opacity: 1; display: block; text-align: left; } } .header-actions { flex: 1; overflow-x: auto; padding: 20px; background: #fff; margin-bottom: 20px; .overflow-box { :deep() .el-button:nth-child(1) { margin-left: 10px; } } } .table-list-container { background: #fff; padding: 15px; .table-pagination { padding-top: 20px; } .header { display: flex; padding-bottom: 20px; } .el-table { :deep() th { font-size: 14px; } :deep() td { font-size: 14px; } } } .by-dropdown-lists { max-height: 50vh; overflow-y: auto; line-height: 1; } :deep(.selectTime .el-input__wrapper) { box-shadow: none; } :deep(.selectTime .el-input__inner) { color: #000 !important; font-size: 14px !important; } :deep(.selectTime .el-input .el-input__icon) { color: #000 !important; font-size: 14px !important; } </style>