带完善内容
提示:二合一,封装el-table以及给表头配置类自定义筛选和排序
文章目录
一、pandas是什么?
el-table.vue
<template>
<div class="page-view">
<el-table v-loading="tableData.tableLoading" ref="multipleTableRef" class="jg-table" :data="tableData.data"
style="width: 100%;" @selection-change="handleSelectionChange" @row-click="rowClick" @sort-change="sortChange">
<template v-for="(item, index) in tableData.columnsData " :key="index">
<!-- 多选 -->
<el-table-column v-if="item.type === 'selection'" :type="item.type" :width="item.width" />
<!-- 单选 -->
<el-table-column v-else-if="item.type === 'radio'" :width="item.width" class-name="no-radio-label">
<template #default="{ row, $index }">
<el-radio @change="changeRadio" v-model="radioIndex" :label="$index"></el-radio>
</template>
</el-table-column>
<!-- 渲染formatter -->
<!-- <el-table-column v-else :property="item.field" :label="item.title" :width="item.width"
:min-width="item.minWidth" :max-idth="item.maxWidth" :showOverflowTooltip="item.showOverflowTooltip"
:sortable="item.sortable" :formatter="item.formatter" /> -->
<el-table-column v-else :property="item.field" :label="item.title" :width="item.width"
:min-width="item.minWidth" :max-idth="item.maxWidth" :showOverflowTooltip="item.showOverflowTooltip"
:sortable="item.sortable" :formatter="item.formatter">
<template #header>
<div class="table-header-div">
<span class="title-span">{{ item.title }}</span>
<!-- 控制筛选按钮显隐 -->
<template v-if="item.field !== 'actionBtn'">
<el-popover :visible="visible && item.field === showKey" :virtual-ref="refName" placement="bottom"
:width="'fit-content'">
<template #reference>
<el-icon class="icon-span" :id="item.field" @click.stop="toggleNameFilter(item.field)"
style="cursor: pointer; margin-left: 10px;"
:style="{ color: searchData[item.field] ? '#007d7b' : '#85888e' }">
<Filter />
</el-icon>
</template>
<div>
<!-- 输入 -->
<el-input v-if="item.formType === 'input'" v-model="searchData[item.field]" placeholder="输入框"
@input="applyNameFilter" clearable style="margin-top: 10px; width: 150px;" />
<!-- 下拉 -->
<el-select v-if="item.formType === 'select' || item.formType === 'radio'" filterable
v-model="searchData[item.field]" placeholder="Select Tag" @change="applyTagFilter" clearable
style="margin-top: 10px; width: 150px;">
<el-option v-for="item in item.selectList" :key="item" :value="item.value" :label="item.name" />
</el-select>
<!-- 多选下拉 -->
<el-select v-if="item.formType === 'selectMultiple'" multiple filterable collapse-tags
v-model="searchData[item.field]" placeholder="Select Tag" clearable
style="margin-top: 10px; width: 150px;">
<el-option v-for="item in item.selectList" :key="item" :value="item.value" :label="item.name" />
</el-select>
<!-- 日期 && 日期时间 -->
<el-date-picker v-if="item.formType === 'daterange' || item.formType === 'datetimerange'"
style="width: 240px;" v-model="searchData[item.field]" :type="item.formType" range-separator="~"
start-placeholder="开始时间" end-placeholder="结束时间" :format="item.dateType"
@change="changeData($event, item.field, item.dateType)" />
<div class="mt" style="text-align: right;">
<el-button type="info" link @click="cancelFilter">重置</el-button>
<el-button type="primary" link @click="searchFilter">筛选</el-button>
</div>
</div>
</el-popover>
</template>
</div>
</template>
</el-table-column>
</template>
</el-table>
<pre>{{ {...searchData,...sortObj} }}</pre>
<pre>{{ tableData }}</pre>
</div>
</template>
<script setup lang="jsx">
import { ref, defineOptions, defineEmits, defineProps, watch, } from 'vue' // 按需引入ref函数
import { Filter } from '@element-plus/icons-vue'
import _ from 'lodash'
import moment from 'moment'
defineOptions({
name: "JgTable"
})
const emit = defineEmits(['setSearchData', 'update:editItem', 'update:handleSelectionChange', 'update:changeRadio', 'update:sortChange'])
const props = defineProps({
description: {
type: String,
default: () => {
return ' '
}
},
tableDataObj: {
type: Object,
default: () => {
return {}
}
},
})
let tableFlag = ref(false)//确保拿到父传子的表格数据再渲染
let tableData = ref({})
let radioIndex = ref(null)
let searchData = ref({}) // 搜索参数 每个表头字段
let sortObj = ref({}) // 排序
// 时间格式化
const changeData = (value, key, dateType) => {
// console.log(value, key, dateType);
if (!_.isEmpty(value)) {
searchData.value[key][0] = value[0] ? moment(value[0]).format(dateType) : ''
searchData.value[key][1] = value[1] ? moment(value[1]).format(dateType) : ''
} else {
searchData.value[key] = []
}
}
const showKey = ref(undefined) // 当前展示哪个筛选窗
const visible = ref(false) // 手动控制筛选窗显隐
const refName = ref(null) // 动态绑定在哪个表头图标下
// 全局重置
const resetFilters = () => {
// searchData.value.nameFilter = {}
for (const key in searchData.value) {
if (Object.hasOwnProperty.call(searchData.value, key)) {
searchData.value[key] = undefined
}
}
applyNameFilter()
applyTagFilter()
getData()
}
// 触发筛选
const toggleNameFilter = (key) => {
if (showKey.value !== key) {
visible.value = false
getData()
}
// console.log(11, document.querySelector(`[ref="${key}"]`));
// showKey.value = document.querySelector(`[ref="${key}"]`)
// console.log(document.getElementById(key));
refName.value = document.getElementById(key)
// refName.value = document.querySelector(`[ref="${key}"]`)
// refName.value = key
showKey.value = key
visible.value = !visible.value
}
// 点击其他元素
const handleClickOutside = () => {
// visible.value = false;
};
// 重置
const cancelFilter = () => {
searchData.value[showKey.value] = undefined
visible.value = false;
getData()
}
// 筛选
const searchFilter = () => {
visible.value = false;
getData()
}
// 单独过滤
const applyNameFilter = () => {
// Filtering logic can be customized if needed
}
const applyTagFilter = () => {
// Filtering logic can be customized if needed
}
const getData = () => {
console.log('筛选参数', { ...searchData.value, ...sortObj.value });
emit('setSearchData', { ...searchData.value, ...sortObj.value })
}
watch([() => props.tableDataObj, () => props.rulesData], ([tableDataObj, rulesData]) => {
// console.log('监听');
tableFlag.value = false
tableData.value = _.cloneDeep(tableDataObj) // 使用lodash的深拷贝方法
tableFlag.value = true
tableDataObj?.columnsData.forEach(ele => {
if (ele.field) {
searchData.value[ele.field] = undefined
}
})
}, { deep: true, immediate: true })
// 多选
const handleSelectionChange = (val) => {
// console.log(val);
// 子传父
emit('update:handleSelectionChange', val);
}
// 单选
const changeRadio = (val) => {
// console.log('单选', val, radioIndex.value.aa);
let item = tableData.value.data?.[val] || {}
emit('update:changeRadio', item);
}
// 表格点击事件
const rowClick = (val) => {
// console.log(val);
}
// 排序
const sortChange = (val) => {
// console.log(val, val.prop, val.order);
sortObj.value = {}
if (!_.isEmpty(val)) {
sortObj.value[val.prop + 'Sort'] = val.order
}
getData() // 直接统一在筛选里加入排序字段
// emit('update:sortChange', sortObj.value) // 不再单独传递排序字段
}
</script>
<style lang="scss" scoped>
:deep(.el-table) {
thead {
.cell {
// background-color: #1fff;
display: flex;
min-width: 100px;
position: relative;
.table-header-div {
// display: inline-block;
// align-items: center;
flex: 1;
display: flex;
// white-space: nowrap;
.title-span {
flex-grow: 1;
word-break: break-word;
/* 允许单词在任何地方断行 */
}
.icon-span {
flex-shrink: 0;
margin-top: 4px;
/* 防止缩小 */
/* 给图标和标题之间添加一些间距 */
}
}
.caret-wrapper {
margin-top: 5px;
}
}
}
}
// :deep(.el-table) {
// thead {
// .cell {
// .table-header-div {
// display: inline-block;
// .title-span {
// }
// }
// .caret-wrapper {
// margin-top: -2px;
// }
// }
// }
// }
// :deep(.el-table) {
// thead {
// .cell {
// // display: flex;
// .table-header-div {
// // flex: 1;
// display: flex;
// .title-span {
// // flex: 1;
// white-space: nowrap!important;
// text-overflow: ellipsis!important;
// overflow: hidden!important;
// word-break: break-all!important;
// }
// .icon-span {}
// }
// .caret-wrapper {
// margin-top: 5px;
// }
// }
// }
// }
// 去除单选框的内容
:deep(.no-radio-label) {
.el-radio__label {
display: none;
}
}
</style>
index.vue
<template>
<JgTable :tableDataObj="tableData" @setSearchData="setSearchData" @update:editItem="updateEditItem"
@update:handleSelectionChange="updateHandleSelectionChange" @update:changeRadio="updateChangeRadio"
@update:sortChange="updateSortChange"></JgTable>
</template>
<script setup lang="jsx">
import { ref, defineComponent, toRefs, reactive, onMounted, getCurrentInstance } from 'vue'
import { Delete, Edit, Download, QuestionFilled, EditPen, Tickets } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
defineOptions({
name: 'EquipmentDefect'
})
const nameList = ref([])
let tableData = ref({
tableLoading: false,
// title:名称 field:key type:单选复选框 width minWidth maxWidth showOverflowTooltip formatter:格式化内容
columnsData: [
{ title: '多选', type: 'selection', width: 55 },
{ title: '单选', type: 'radio', width: 55 },
{ title: '缺陷名称', field: 'aa', formType: 'input', sortable: 'custom', width: 120 },
{ title: '缺陷描述', field: 'bb', formType: 'input', sortable: 'custom', showOverflowTooltip: true },
{ title: '缺陷发布时间', field: 'cc1', formType: 'daterange', sortable: 'custom', dateType: 'YYYY-MM-DD', width: 140 },
{ title: '缺陷发布时间2', field: 'cc2', formType: 'datetimerange', sortable: 'custom', dateType: 'YYYY-MM-DD HH:mm:ss', width: 150 },
// { title: '缺陷发布时间', field: 'cc1', formType: 'datetime',sortable: 'custom', dateType: 'YYYY-MM-DD HH:mm:ss' },
{
title: '设备类型', field: 'dd1', formType: 'select', sortable: 'custom', selectList: nameList,
formatter: (row) => {
const val = nameList.value.find(item => item.value === row.dd1)?.name || ''
return [<span>{val}</span>]
}
},
{
title: '设备类型多选', field: 'dd2', formType: 'selectMultiple', sortable: 'custom', width: 120, selectList: nameList,
formatter: (row) => {
const val = filters(row.dd2, nameList.value)
return [<span>{val}</span>]
}
},
{ title: '设备厂家', field: 'ee', formType: 'input', sortable: 'custom', },
{ title: '设备版本', field: 'ff', formType: 'input', sortable: 'custom', width: 160, showOverflowTooltip: true },
{
title: '缺陷设备数量缺陷设备数量', field: 'gg', formType: 'input', sortable: 'custom', width: 160,
formatter: (row, column) => {
// console.log(112,row, column); // 直接{row}或{column} 不会显示内容
return [<span onClick={() => clickItem(row, 'view')}> {row.gg} </span>]
}
},
{
title: '缺陷反馈状态', field: 'hh', formType: 'radio', sortable: 'custom', minWidth: 200, selectList: [
{ name: '已反馈', value: 1 },
{ name: '未反馈', value: 0 },
],
formatter: (row) => (
row ? <el-tag style="border: none;" class="cursor" type={row.hh === 1 ? 'primary' : 'warning'}>{row.hh === 1 ? '已反馈' : row.hh === 0 ? '未反馈' : ''}</el-tag> : ''
)
},
{
title: '缺陷处置进度', field: 'ii', formType: 'progress', sortable: 'custom',
formatter: (row) => (
<el-progress class="table_progress" width={13} stroke-width={2} type="circle" percentage={row.ii} />
)
},
// {
// title: '测试', field: 'ceshi',width: 200,
// formatter: (row, column) => {
// // console.log(112,row, column); // 直接{row}或{column} 不会显示内容
// return [<el-tag type={row.ceshi == "可发放" ? "success" : "danger"} onClick={() => editItem(row, 'view')}> {row.ceshi} </el-tag>, <span>{column.label}</span>]
// }
// },
{
title: '操作', field: 'actionBtn', width: 220,
formatter: (row) => (
[
<el-button link type="primary" size="small" icon={Tickets} onClick={() => editItem(row, 'view')} >查看</el-button >,
<el-button link type="primary" size="small" icon={EditPen} onClick={() => editItem(row, 'edit')} >编辑</el-button >,
<el-button link type="primary" size="small" icon={Delete} onClick={() => editItem(row, 'delete')} >删除</el-button >,
]
)
},
],
data: Array.from({ length: 7 }).map((ele, index) => {
return {
aa: '客户端不适配',
bb: '客户端无法适配凝思 4.2.40',
cc1: '2023-7-20',
cc2: '2023-7-20 10:10:10',
dd1: Math.floor(Math.random() * 5),
dd2: [1, 2],
ee: '帕拉迪',
ff: 'PLD Sys 61012100',
gg: Math.floor(Math.random() * 20),
hh: Math.floor(Math.random() * 2),
ii: Math.floor(Math.random() * 101),
ceshi: 123,
}
})
})
const clickItem = (row, type) => {
// console.log(row, type);
ElMessage({
type: 'success',
message: '点击',
})
}
// 编辑或查看
const editItem = (row, type) => {
switch (type) {
case 'view':
case 'edit':
console.log(row, type);
break;
case 'delete':
deleteItem(row, type)
break;
default:
break;
}
}
// 删除
const deleteItem = (row, type) => {
ElMessageBox.confirm(
'确定删除该列?',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(() => {
ElMessage({
type: 'success',
message: 'Delete completed',
})
})
.catch(() => {
})
}
// 拿到搜索条件
const setSearchData = (data) => {
console.log(data);
}
// 子组件调用父组件的测试事件
const updateEditItem = (data) => {
console.log('子组件调用父组件的测试事件', data);
editItem(data.row, data.type)
}
const updateHandleSelectionChange = (data) => {
console.log('updateHandleSelectionChange', data);
}
const updateChangeRadio = (data) => {
console.log('updateChangeRadio', data);
}
const updateSortChange = (data) => {
// console.log(data);
}
// 下拉过滤回显
const filters = (key, arr = []) => {
const names = []
const value = Array.isArray(key) ? key : [key]
for (let i = 0; i < arr.length; i++) {
if (value.includes(arr[i].value)) {
names.push(arr[i].name)
}
}
return names.join(';')
}
onMounted(() => {
setTimeout(() => {
nameList.value = [
{ name: '横向隔离', value: 0 },
{ name: '纵向加密', value: 1 },
{ name: '防火墙', value: 2 },
{ name: '监测装置', value: 3 },
{ name: '运维网关', value: 4 },
{ name: '其他', value: 5 },
]
}, 1000);
})
// 将变量和函数返回,以便在模版中使用
</script>
<style lang="scss" scoped></style>