“El-Table二次封装“这样做【高级前端必备技能之一】

🔥 前言

这篇文章给大家分享一个高级自定义列表组件从0到1的开发过程,这个列表组件的主要功能有,列表拖拽排序,右侧操作按钮统一使用Tooltip展示,操作表头增加自定列表icon,点击icon可以对列表展示数据进行是否显示、排序等操作,契合业务需求,增加表格美观以及复用性。

🔥关与自定义表格

随着系统业务复杂度的提高,列表需要展示的数据变得复杂,常见的El-table逐渐不能满足我们系统的日常使用,更为关键的是El-table 在使用的过程种比较复杂,需要书写大量的< ,并且不能满足我们的系统UI,所以我们决定对El-table进行高度的自定义二次封装,使得团队同学在使用的时候更加便捷、容易,同时也满足了我们系统统一页面风格的需求,下面就给大家介绍我们实现的详细过程。

🔥效果图展示:

在这里插入图片描述✨表格拖转排序
在这里插入图片描述
✨右侧操作按钮显示UI
在这里插入图片描述
✨自定义表头字段弹框
在这里插入图片描述

🔥基本使用

index.vue

<template>
	<!-- 列表 -->
	<div style="height: calc(100% - 155px)">
		<h-table
		    ref="selectionTableRef"
		    v-loading="loading"
		    :border="true"
		    :columns="state.table.columns"
		    :custom-list="customList"
		    :is-custom-list="true"
		    :table="state.table"
		    operatorTheme="useless"
		    stripe
		    @saveCustomList="saveCustomList"
		</h-table>
	</div>
</template>
<script setup>
const state = reactive({
    table: {
        total: 0,
        pageNo: 1,
        pageSize: 10,
        pageSizes: [10, 20, 50, 100],
        // 接口返回数据
        data: [],
        // 表头数据
        columns: [],
        // 多选
        firstColumn: { type: 'selection', fixed: 'left' },
        // 操作列样式
        operatorConfig: {
            fixed: 'right', // 固定列表右边(left则固定在左边)
            width: 100,
            label: '操作',
        },
        operator: [
            {
                text: '查看',
                fun: (val) => look([val]),
                show: [{ key: 'status', val: ['new'] }],
            },
            {
                text: '编辑',
                style: { color: '#f56c6c' },
                fun: (val) => edit([val]),
                show: [{ key: 'status', val: ['new'] }],
            },
        ],
    },
})
// 列配置
const customList = reactive({
	//列表数据
    allColumns: [
        {
            label: '姓名',
            prop: 'name',
            key: 'name',
            width: 150,
            fixed: 'left',
        },
        {
            label: '性别',
            prop: 'sex',
            key: 'sex',
            fixed: 'left',
            minWidth: 280,
        },
        {
            label: '年龄',
            prop: 'age',
            key: 'age',
            minWidth: 100,
        },
        {
            label: '时间',
            prop: 'date',
            key: 'date',
            minWidth: 160,
        }
    ],
    //自定义表头左侧数据
    allData: [
        {
            title: '全部',
            children: [
                { key: 'name', title: '姓名'},
                { key: 'sex', title: '性别'},
                { key: 'age', title: '年龄' },
                { key: 'date', title: '时间' },
            ],
        },
    ],
    //自定义表头右侧可拖拽数据
    defaultCheckData: [
        { key: 'name',title: ['姓名'] },
        { key: 'sex', title: ['性别'] },
        { key: 'age', title: ['年龄'] },
        { key: 'date', title: ['时间'] },
    ],
})

</script>

🔥 碎碎念

看了上面的组件使用是不是觉得,使用起来非常简单,而且在模板层面可以减少很多HTML内容的书写

🔥内部实现

<template>
	<div class="h-table">
		<el-table ref="TTable" :data="state.tableData" :scrollbar-always-on="scrollbarAlwaysOn" :size="size"
			:highlight-current-row="highlightCurrentRow" :border="border || table.border || isTableBorder"
			@cell-dblclick="cellDblclick"
			@row-click="rowClick" :cell-class-name="cellClassNameFuc" :tooltip-options="tooltipOptions" v-bind="{
      ...$attrs,
      class: {
        cursor: isCopy,
        highlightCurrentRow: highlightCurrentRow,
        radioStyle: table.firstColumn && table.firstColumn.type === 'radio',
        outerBorder: !border && !(table.border || isTableBorder),
      },
      style: ''
    }" style="height: 100%;">
			<!-- 行拖拽列 -->
			<template v-if="table.lockColumn">
				<el-table-column type="lock" :width="table.lockColumn.width || 55" :fixed="table.lockColumn.fixed">
					<template #header>
						<slot :name="table.lockColumn.slotName + '_header'">
							<el-icon @click.stop="lockChange($event)">
								<Lock v-show="isLock" :style="{ 'color': table.lockColumn.lockDefaultColor || '#006ef0' }"
									class="pointCursor" />
								<Unlock v-show="!isLock" :style="{ 'color': table.lockColumn.lockActiveColor || '#3ccda0' }"
									class="pointCursor" />
							</el-icon>
						</slot>
					</template>
					<template #default="scope">
						<!-- 自定义插槽 -->
						<slot :name="table.lockColumn.slotName" :scope="scope">
							<el-icon>
								<Rank
									:style="{ 'color': isLock ? table.lockColumn.rankDefaultColor || '#8d9399' : table.lockColumn.rankActiveColor || '#006ef0' }"
									:class="{ 'pointCursor': isLock ? false : true }" />
							</el-icon>
						</slot>
					</template>
				</el-table-column>
			</template>
			<!-- 复选框/单选框/序列号 -->
			<template v-if="table.firstColumn">
				<!-- 复选框 -->
				<el-table-column v-if="table.firstColumn.type === 'selection'" :type="table.firstColumn.type"
					:width="table.firstColumn.width || 55" :reserve-selection="table.firstColumn.isPaging || false"
					:label="table.firstColumn.label" :align="table.firstColumn.align || 'center'" :fixed="table.firstColumn.fixed"
					:selectable="table.firstColumn.selectable" />
				<!-- 单选框 -->
				<el-table-column v-if="table.firstColumn.type === 'radio'" :type="table.firstColumn.type"
					:width="table.firstColumn.width || 55" :label="table.firstColumn.label" :fixed="table.firstColumn.fixed"
					:align="table.firstColumn.align || 'center'">
					<template #default="scope">
						<el-radio v-model="radioVal" :value="scope.$index + 1"
							@click.stop="radioChange($event, scope.row, scope.$index + 1)"></el-radio>
					</template>
				</el-table-column>
				<!-- 序列号 -->
				<el-table-column v-if="table.firstColumn.type === 'index'" :type="table.firstColumn.type"
					:width="table.firstColumn.width || 55" :label="table.firstColumn.label || '序号'"
					:fixed="table.firstColumn.fixed" :align="table.firstColumn.align || 'left'">
					<template #default="scope">
              			{{isShowPagination? (table.pageNo - 1) * table.pageSize + scope.$index + 1: scope.$index + 1}}
					</template>
				</el-table-column>
			</template>
			<!-- 主体内容 -->
			<template v-for="(item, index) in renderColumns">
				<template v-if="!item.children">
					<!-- 常规列 -->
					<el-table-column v-if="item.permission === falseitem.permission : true" :key="index + 'i'"
						:type="item.type" :label="item.label" :prop="item.prop" :width="item.width" :min-width="item.minWidth || 90"
						:sortable="item.sortable" :align="item.align || 'left'" :fixed="item.fixed"
						:show-overflow-tooltip="item.noShowTip === false ? item.noShowTip : true">
						<template #header v-if="item.slotHeader">
							<slot :name="item.prop + '_header'">
								{{ item.label }}
							</slot>
						</template>
						<template #default="scope">
							<!-- formatter渲染 -->
							<template v-if="item.formatter">
								{{ item.formatter({ [item.prop]: scope.row[item.prop], item: scope.row, index: scope.$index }) }}
							</template>
							<!-- render渲染 -->
							<template v-if="item.render">
								<render-col :column="item" :row="scope.row" :render="item.render" :index="scope.$index" />
							</template>
							<!-- 自定义插槽 -->
							<template v-if="item.slotName">
								<slot :name="item.slotName" :scope="scope" :index="scope.$index"></slot>
						</template>
					</el-table-column>
				</template>
			</template>
			<slot></slot>
			<!-- 操作按钮 -->
			<template>
				<el-table-column v-if="table.operator || table.operatorConfig?.onlySetting" :fixed="table.operatorConfig?.fixed"
					:width="56" :min-width="56" :align="table.operatorConfig?.align || 'left'"
					class-name="operator operator_useless">
					<template #header>
						<div class="operator-menu" :class="{ 'operator-menu-disable': !customList.allData.length }"
							v-if="isCustomList">
							<el-icon class="icons" @click="openCustomList">
								<Setting />
							</el-icon>
						</div>
					</template>
					<template #default="scope">
						<el-popover placement="left" effect="customized" popper-class="mtable_operator_useless_popover" :offset="0"
							trigger="hover" v-if="operatorList(scope).length">
							<template #reference>
								<!--@click="scope.row.popVisible = !scope.row.popVisible"-->
								<div class="useless-popover-icon">
									<el-icon>
										<Operation />
									</el-icon>
								</div>
							</template>
							<template #default>
								<div class="operator_useless_btn" v-for="(item, index) in operatorList(scope)" :key="index">
									<template v-if="!item.slot">
										<m-button @click="clickOperationBtn(item, scope.row, scope.$index)"
											:type="item.type ? item.type : 'primary'" link :style="filStyle(item)"
											:icon="item.icon ? item.icon : ''" :disabled="item.disabled"
											:size="item.size ? item.size : 'default'" :title="item.title"
											:class="{ defbtn: !item.disabled && !item.style }">
											<!-- render渲染 -->
											<template v-if="item.render">
												<render-col :column="item" :row="scope.row" :render="item.render" :index="scope.$index" />
											</template>
											<span v-if="!item.render">{{ item.text }}</span>
										</m-button>
									</template>
									<!-- 插槽 -->
									<template v-else>
										<slot :name="item.slot" :scope="{ row: scope.row }" :index="scope.$index"></slot>
									</template>
								</div>
							</template>
						</el-popover>
					</template>
				</el-table-column>
			</template>
		</el-table>
		<!-- 自定义列表弹窗 -->
		<m-custom-list ref="customlisttable" :allData="customList.allData" :append-to-body="appendToBody"
			@save="saveCustomList" />
	</div>
</template>
<script setup>
import { ElMessage } from 'element-plus'
import { get } from 'lodash-es'
import { Setting, CaretBottom, Operation, MoreFilled, Unlock, Rank, Lock } from '@element-plus/icons-vue'
import RowDrag from 'sortablejs'
defineOptions({
	name: 'HTable',
})

const props = defineProps({
	// table所需数据
	table: {
		type: Object,
		default: () => {
		},
		required: true,
	},
	// 表头数据
	columns: {
		type: Array,
		default: () => [],
		// required: true
	},
	// 表格标题
	title: {
		type: String,
	},
	// 是否复制单元格
	isCopy: {
		type: Boolean,
		default: false,
	},
	// 是否开启点击整行选中单选框
	rowClickRadio: {
		type: Boolean,
		default: true,
	},
	// 是否开启编辑保存按钮
	isShowFooterBtn: {
		type: Boolean,
		default: false,
	},
	// 是否高亮选中行
	highlightCurrentRow: {
		type: Boolean,
		default: false,
	},
	// 是否开启合计行隐藏复选框/单选框/序列
	isTableColumnHidden: {
		type: Boolean,
		default: false,
	},
	border: {
		type: Boolean,
		default: false,
	},
	// 尺寸风格
	size: {
		type: String,
		default: 'default',
	},
	// tooltip风格配置
	tooltipOptions: {
		type: Object,
		default: () => {
			return {
				effect: 'light',
				offset: 0,
			}
		},
	},
	// 是否需要自定义列表操作
	isCustomList: {
		type: Boolean,
		default: false,
	},
	// 自定义列表配置
	customList: {
		type: Object,
		default: () => {
			return {
				allData: [],
			}
		},
	},
	// 默认选中的数据
	defRadioObj: {
		type: Object,
		default: () => {
		},
	},
	// 按钮权限数组
	btnPremList: {
		type: Array,
		default: () => [],
	},
	// 是否长显滚动条
	scrollbarAlwaysOn: {
		type: Boolean,
		default: true,
	},
	// 自定义列表弹窗是否放到body下
	appendToBody: {
		type: Boolean,
		default: true,
	},
	// 操作栏样式主题 default-默认、useless
	operatorTheme: {
		type: String,
		default: 'default',
	},
})
// 初始化数据
let state = reactive({
	tableData: props.table?.data || [],
	columnSet: [],
})

// 单选框
const radioVal = ref(null)
// 判断单选选中及取消选中
const forbidden = ref(true)
// 获取ref
const TTable = ref(null)
// 抛出事件
const emits = defineEmits([
	'save',
	'size-change',
	'page-change',
	'handleEvent',
	'radioChange',
	'saveCustomList',
	'lockChange',
	'dropRow',
])
// 获取所有插槽
const slots = useSlots()
watch(
	() => [props.table?.data, props.defRadioObj],
	(val) => {
		state.tableData = val[0]
		radioVal.value = null // 重置选中下标
		if (val[0]?.length && val[1] && Object.keys(val[1]).length) {
			const obj = deepClone(val[1])
			const _key = obj.key
			const _value = obj.value
			if (_value === undefined || _value === null) {
				radioVal.value = null
			} else {
				val[0].forEach((it, idx) => {
					if (it[_key] === _value) {
						radioVal.value = idx + 1
					}
				})
			}
		} else {
			radioVal.value = null
		}
	},
	{ immediate: true, deep: true },
)
// 处理操作按钮,判断权限且整合是否展示更多
const operatorList = computed(() => {
	return function(scope) {
		// console.log('scope: ', scope);
		const _op = props.table.operator
		// 过滤掉没有不展示的数据
		const _nop = []
		_op.forEach((_opit, _opidx) => {
			if (checkIsShow(scope, _opit)) {
				_nop.push({ ..._opit, popVisible: false })
			}
		})
		let moreList = []
		if (props.operatorTheme === 'default') {
			// 处理到“更多”
			if (_nop.length <= 2) return _nop
			let newArray = {
				more: true,
				children: [..._nop.slice(2)],
			}
			moreList = [_nop[0], _nop[1], newArray]
		} else {
			moreList = _nop
		}
		return moreList
	}
})
// 点击操作按钮的回调
const clickOperationBtn = (item, scoprow, scopindex) => {
	// console.log(item, scoprow, scopindex)
	// scoprow.popVisible = false
	return item.fun && item.fun(scoprow, scopindex, state.tableData)
}
// 更多按钮展开/收起操作-选中效果
const isOpenMorebtn = ref(false)
const openMoreIndex = ref(null)
const changeMorebtn = (e, index) => {
	isOpenMorebtn.value = e
	openMoreIndex.value = index
}
// 处理按钮颜色
const filStyle = computed(() => {
	return function(item) {
		const _color = props.operatorTheme === 'default' ? 'color: #006ef0' : 'color: #fff'
		return !item.disabled ? item.style ? item.style : _color : ''
	}
})
// 判断如果有表头合并就自动开启单元格缩放
const isTableBorder = computed(() => {
	return props.columns.some((item) => item.children)
})
// 处理回显数据
const fileValue = computed(() => {
	return function(row, prop, unit) {
		const _data = get(row, prop)
		return _data || _data === 0 ? `${ _data }${ unit || '' }` : '/'
	}
})
// 合并行隐藏复选框/单选框
const cellClassNameFuc = ({ row }) => {
	if (!props.isTableColumnHidden) {
		return false
	}
	if (
		state.tableData.length -
		(state.tableData.length - props.table.pageSize < 0
			? 1
			: state.tableData.length - props.table.pageSize) <=
		row.rowIndex
	) {
		return 'table_column_hidden'
	}
}
// forbidden取值(选择单选或取消单选)
const isForbidden = () => {
	forbidden.value = false
	setTimeout(() => {
		forbidden.value = true
	}, 0)
}
// 单选抛出事件radioChange
const radioClick = (row, index) => {
	forbidden.value = !!forbidden.value
	if (radioVal.value) {
		if (radioVal.value === index) {
			radioVal.value = null
			isForbidden()
			// 取消勾选就把回传数据清除
			emits('radioChange', null, radioVal.value)
		} else {
			isForbidden()
			radioVal.value = index
			emits('radioChange', row, radioVal.value)
		}
	} else {
		isForbidden()
		radioVal.value = index
		emits('radioChange', row, radioVal.value)
	}
}
// 判断是否使用漏了某个插槽
const isShow = (name) => {
	return Object.keys(slots).includes(name)
}
// 整行编辑返回数据
const save = () => {
	emits('save', state.tableData)
	return state.tableData
}
const onMouseOver = (event, item) => {
	const { offsetWidth, offsetLeft } = event.target
	const pOffsetWidth = event.fromElement.offsetWidth
	const width = item.minWidth ? item.minWidth : pOffsetWidth
	// console.log(event)
	width < offsetWidth + offsetLeft * 2 ? (item.showSelfTip = true) : (item.showSelfTip = false)
}
/**
 * 公共方法
 */
// 清空排序条件
const clearSort = () => {
	return TTable.value.clearSort()
}
// 取消某一项选中项
const toggleRowSelection = (row, selected = false) => {
	return TTable.value.toggleRowSelection(row, selected)
}
// 清空复选框
const clearSelection = () => {
	return TTable.value.clearSelection()
}
const customlisttable = ref(null)
// 打开自定义列表
const openCustomList = () => {
	if (props.customList.allData.length) {
		customlisttable.value.open(props.customList.defaultCheckData)
	}
}
// 提交自定义列表的保存的数据
const saveCustomList = (val) => {
	emits('saveCustomList', val)
}
// 重新布局表格
const doLayout = () => {
	TTable.value.doLayout()
}
// 解锁或者锁定行拖拽
const isLock = ref(props.table?.lockColumn?.isLock || true)
const lockChange = (val) => {
	isLock.value = !isLock.value
	if (isLock.value) {
		destroyDrop()
	} else {
		rowDrop()
	}
	emits('lockChange', isLock.value)
}
const tbodyObj = ref(null)
const tbody = ref(null)
// 拖拽传参
const rowDrop = () => {
	if (isLock.value) return
	tbody.value = document.querySelector(".el-table__body-wrapper tbody")
	if (tbody.value) {
		tbodyObj.value = RowDrag.create(tbody.value, {
			animation: 300,
			onEnd: ({ newIndex, oldIndex }) => {
				emits('dropRow', {
					oldIndex: oldIndex,
					newIndex: newIndex,
					data: TTable.value,
				})
			},
		})
	}
}
// 销毁RowDrag
const destroyDrop = () => {
	if (tbodyObj.value instanceof RowDrag) {
		tbodyObj.value.destroy()
	}
}
onMounted(() => {
	rowDrop()
})
// 暴露方法出去
defineExpose({ clearSelection, toggleRowSelection, clearSort, doLayout })
</script>

🔥Style 样式

<style lang="scss" scoped>
$table-border-color: #f0f3f5;
.h-table {
	height: 100%;
	z-index: 0;

	:deep(.el-table::before) {
		background: none;
	}

	:deep(.el-table__body-wrapper) {
		background: #f7fbfe;

		.el-table__body {
			margin: 0;

			// 操作按钮部分
			.operator {
				.cell {
					height: 100%;
					display: flex;
					align-items: center;
					padding: 0 !important;
				}

				&_btn {
					height: 100%;
					display: flex;
					align-items: center;
					padding: 0 10px;

					.el-button {
						// width: 82px;
						height: 16px;
						margin: 0;
						padding: 0 10px;
						border-right: 1px solid #dadee6;

						&:first-child {
							padding-left: 0;
						}

						&:last-child {
							border: none;
							padding-right: 0;

							&:hover {
								border: none;
							}
						}

						&:hover {
							border-right: 1px solid #dadee6;
						}
					}

					.oper_mor_btn {
						width: 40px;
						height: 100%;
						padding-left: 0;

						& > span {
							width: 100%;
							height: 100%;
						}

						.morebtn {
							width: 100%;
							height: 100%;
							display: flex;
							justify-content: center;
							align-items: center;

							&-title {
								width: 100%;
								height: 100%;
								display: inline-flex;
								justify-content: center;
								align-items: center;

								& > i {
									color: #8d9399;
									transform: rotate(90deg);
									margin-left: -2px;
								}

								&:focus-visible {
									outline: none;
								}
							}
						}
					}

					.oper_mor_btn_active {

						// background: #61a3f2;
						.morebtn {
							&-title {
								& > i {
									// color: #fff;
									color: #61a3f2;
								}
							}
						}
					}
				}
			}

			// useless模式时的操作
			.operator_useless {
				.cell {
					.useless-popover-icon {
						width: 100%;
						height: 100%;
						display: flex;
						align-items: center;
						justify-content: center;
						cursor: pointer;

						& > .el-icon {
							font-size: 16px;
						}

						&:hover {
							& > .el-icon {
								color: #61a3f2;
							}
						}
					}
				}
			}
		}
	}

	:deep(.mpagination) {
		margin-top: 12px;
		background-color: transparent;
	}

	:deep(.el-popper) {
		max-width: 600px;
	}

	// 某行隐藏复选框/单选框
	:deep(.el-table) {
		.el-popper {
			font-size: 14px;
		}
		.el-table__row {
			.table_column_hidden {
				.cell {

					.el-radio__input,
					.el-checkbox__input {
						display: none;
					}

					& > span {
						display: none;
					}
				}
			}
		}
	}

	.el-table th,
	.el-table td {
		padding: 8px 0;
	}

	.el-table--border th:first-child .cell,
	.el-table--border td:first-child .cell {
		padding-left: 5px;
	}

	.el-table--scrollable-y .el-table__fixed-right {
		right: 8px !important;
	}

	.header_wrap {
		display: flex;
		align-items: center;

		.toolbar_top {
			flex: 0 70%;
			display: flex;
			align-items: center;
			justify-content: flex-end;

			.toolbar {
				display: flex;
				justify-content: flex-end;
				width: 100%;
			}

			.el-button--small {
				height: 32px;
			}

			.el-button--success {
				background-color: #355db4;
				border: 1px solid #355db4;
			}
		}

		.header_title {
			display: flex;
			align-items: center;
			flex: 0 30%;
			font-size: 16px;
			font-weight: bold;
			line-height: 35px;
			margin-left: 10px;
		}
	}

	.marginBttom {
		margin-bottom: -8px;
	}

	// 表格外边框
	.outerBorder {
		border: 1px solid $table-border-color;
	}

	// 单选样式
	.radioStyle {
		:deep(.el-table__header) {
			.el-table__cell:first-child {
				border-right: 0;

				.cell {
					display: none;
					border-right: 0 !important;
				}
			}
		}

		:deep(.el-radio) {
			&:focus:not(.is-focus):not(:active):not(.is-disabled) .el-radio__inner {
				box-shadow: none;
			}
		}

		:deep(tbody) {
			.el-table__row {
				cursor: pointer;
			}
		}
	}

	// 复制功能样式
	.cursor {
		:deep(tbody) {
			.el-table__row {
				cursor: pointer;
			}
		}
	}

	// 表格样式调整
	:deep(.el-table) {
		.el-table__header {
			margin: 0;

			.el-table__cell {
				height: 50px;
				font-size: 16px;
				background: #e5f0fd;
				border-right: 1px solid #ecf4fe;

				.cell {
					height: 26px;
					line-height: 26px;
					color: #666;
					font-weight: normal;
				}

				&:last-child {
					border-right: 0;
				}
			}

			.el-table-column--selection {
				.cell {
					border-right: 0 !important;
				}
			}
		}

		.el-table__body-wrapper {
			.el-scrollbar__view {
				height: 100%;
			}

			.el-table__body {
				.el-table__row {
					.el-table__cell {
						height: 40px;
						padding: 0;
						border-bottom: 1px solid #e7edf9;
						border-right: 0 !important;

						// 取消展开图标的旋转
						.el-table__expand-icon {
							.el-icon {
								display: none;
							}

							transform: rotate(0deg);

							&::before {
								content: '';
								display: block;
								width: 16px;
								height: 16px;
								margin-top: -1px;
								background: url('../../../images/open.png') no-repeat center top;
								background-size: 100% 100%;
							}
						}

						// 展开节点
						.el-table__expand-icon--expanded {
							&::before {
								content: '';
								width: 16px;
								height: 16px;
								margin-top: -1px;
								background: url('../../../images/up.png') no-repeat center top;
								background-size: 100% 100%;
							}
						}
					}
				}
			}

			&.el-table--default .cell {
				padding: 0 16px;
			}

			.el-table__row--striped {
				.el-table__cell {
					background: #f5f9fe;
				}
			}
		}

	}

	:deep(.el-table.el-table--border) {
		.el-table__header {
			.el-table__cell {
				.cell {
					border-right: 1px solid #bcd0f2;
					color: #282d32;
					font-weight: 500;
				}
			}

			.is-group {
				.el-table__cell {
					.cell {
						border-right: 0;
					}
				}
			}
		}
	}

	// 操作头部
	.operator {
		.cell {
			padding: 0 !important;

			// 操作样式
			.operator-title {
				display: flex;
				// justify-content: center;
				align-items: center;
			}

			.operator-menu {
				width: 20px;
				height: 20px;
				position: absolute;
				right: 16px;
				top: 15px;
				z-index: 2;
				cursor: pointer;

				.icons {
					color: #505363;
					font-size: 20px;

					&:hover {
						color: #409eff;
					}
				}
			}

			.operator-menu-disable {
				cursor: default;


				.icons {

					&:hover {
						color: #505363;
					}
				}
			}
		}
	}

	.operator_useless {
		.cell {
			.operator-menu {
				right: 18px;
			}
		}
	}

	// 页面缓存时,表格内操作栏每行高度撑满
	:deep(.el-table__fixed-right) {
		height: 100% !important;
	}

	// 选中行样式
	.highlightCurrentRow {
		tbody {
			:deep(.el-table__row) {
				cursor: pointer;
			}

			.current-row td {
				cursor: pointer;
				color: #fff;
				background-color: #355db4 !important;
			}
		}
	}

	.el-table--scrollable-y .el-table__body-wrapper {
		overflow-x: auto;
	}

	.handle_wrap {
		position: sticky;
		z-index: 10;
		right: 0;
		bottom: -8px;
		margin: 0 -8px -8px;
		padding: 12px 16px;
		background-color: #fff;
		border-top: 1px solid #ebeef5;
		text-align: right;

		.el-btn {
			margin-left: 8px;
		}
	}

	.pointCursor {
		cursor: pointer;
	}
}
</style>
<style lang="scss">
.morebtn-popper {
	.el-dropdown-menu {
		padding: 4px !important;

		.el-dropdown-menu__item {
			height: 32px;

			&:hover {
				background: #eaf3fc;
			}

			.el-button {
				width: 100%;
				height: 100%;
			}
		}
	}
}

.mtable_operator_useless_popover {
	width: inherit !important;
	display: flex;
	margin-bottom: -4px;
	padding: 11px 4px !important;
	min-width: inherit !important;

	&.is-customized {
		background: rgba(99, 108, 128, 0.9) !important;

		.el-popper__arrow::before {
			background: rgba(99, 108, 128, 0.9) !important;
		}
	}

	.operator_useless_btn {
		height: 16px;
		display: flex;
		align-items: center;
		margin: 0 12px 0;
		position: relative;

		.el-button {
			font-size: 14px;
			padding: 0;
		}

		.el-button.defbtn:hover {
			color: #52abff !important;
		}

		&:not(:first-child)::before {
			content: '';
			position: absolute;
			width: 1px;
			height: 12px;
			background: #949ba9;
			left: -12px;
			top: 50%;
			transform: translateY(-50%);

		}
	}
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/786673.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

玩具营销是如何拿捏成年人钱包?

好像现在的成年人逐渐热衷于偏向年轻化&#xff0c;问问题会好奇“尊嘟假嘟”&#xff0c;饭量上的“儿童套餐”&#xff0c;娃娃机前排长队......而最突出的莫过于各类各式的玩具不断收割当代年轻人&#xff0c;除去常给大朋友们小朋友们送去玩具福利的“麦、肯”双门&#xf…

nvm安装使用 nrm使用

因维护老项目及开发新项目同时进行&#xff0c;需要使用不同版本的node进行运行&#xff0c;所以用nvm进行多个版本的node维护&#xff0c;通过nrm进行镜像源管理切换 简介 Node.js 是一种基于 Chrome V8 引擎的 JavaScript 运行环境&#xff0c;用于构建高性能的网络应用程序…

Linux--线程的控制

目录 0.前言 1.pthread库 2.关于控制线程的接口 2.1.创建线程&#xff08;pthread_create&#xff09; 2.2.线程等待&#xff08;pthread_join&#xff09; 代码示例1&#xff1a; ​编辑 ***一些问题*** 2. 3.创建多线程 3.线程的终止 &#xff08;pthread_exit /…

用LangGraph、 Ollama,构建个人的 AI Agent

如果你还记得今年的 Google I/O大会&#xff0c;你肯定注意到了他们今年发布的 Astra&#xff0c;一个人工智能体&#xff08;AI Agent&#xff09;。事实上&#xff0c;目前最新的 GPT-4o 也是个 AI Agent。 现在各大科技公司正在投入巨额资金来创建人工智能体&#xff08;AI …

逻辑回归模型(非回归问题,而是解决二分类问题)

目录&#xff1a; 一、Sigmoid激活函数&#xff1a;二、逻辑回归介绍&#xff1a;三、决策边界四、逻辑回归模型训练过程&#xff1a;1.训练目标&#xff1a;2.梯度下降调整参数&#xff1a; 一、Sigmoid激活函数&#xff1a; Sigmoid函数是构建逻辑回归模型的重要激活函数&am…

C++ | Leetcode C++题解之第221题最大正方形

题目&#xff1a; 题解&#xff1a; class Solution { public:int maximalSquare(vector<vector<char>>& matrix) {if (matrix.size() 0 || matrix[0].size() 0) {return 0;}int maxSide 0;int rows matrix.size(), columns matrix[0].size();vector<…

Java基础语法--基本数据类型

Java基础语法–基本数据类型 Java是一种静态类型语言&#xff0c;这意味着每个变量在使用前都必须声明其数据类型。Java提供了多种基本数据类型&#xff0c;用于存储整数、浮点数、字符和布尔值等。以下是Java中的基本数据类型及其特点&#xff1a; 1. 整型&#xff08;Integ…

Java面试八股之MySQL中的MVCC是什么,作用是什么?

MySQL中的MVCC是什么&#xff0c;作用是什么&#xff1f; MySQL中的MVCC&#xff08;Multiversion Concurrency Control&#xff0c;多版本并发控制&#xff09;是一种并发控制机制&#xff0c;用于提高数据库的并发性能并确保数据的一致性&#xff0c;特别是在高并发读写场景…

python调用qt编写的dll

报错&#xff1a;FileNotFoundError: Could not find module F:\pythonProject\MINGW\sgp4Lib.dll (or one of its dependencies). Try using the full path with constructor syntax. 只有两种情况&#xff1a; 1.路径不对 2.库的依赖不全 1、如果是使用了qt库的&#xff0…

使用tkinter拖入excel文件并显示

使用tkinter拖入excel文件并显示 效果代码 效果 代码 import tkinter as tk from tkinter import ttk from tkinterdnd2 import TkinterDnD, DND_FILES import pandas as pdclass ExcelViewerApp(TkinterDnD.Tk):def __init__(self):super().__init__()self.title("Excel…

unity 手动制作天空盒及使用

提示&#xff1a;文章有错误的地方&#xff0c;还望诸位大神不吝指教&#xff01; 文章目录 前言一、使用前后左右上下六张图1.准备6张机密结合的图片2.创建Material材质球3.使用天空盒 二、使用HDR贴图制作1.准备HDR贴图2.导入unity 修改Texture Sourpe 属性3.创建材质球4.使用…

自定义刷题工具-python实现

背景&#xff1a; 最近想要刷题&#xff0c;虽然目前有很多成熟的软件&#xff0c;网站。但是能够支持自定义的导入题库的非常少&#xff0c;或者是要么让你开会员&#xff0c;而直接百度题库的话&#xff0c;正确答案就摆在你一眼能看见的地方&#xff0c;看的时候总觉得自己…

使用Python绘制QQ图并分析数据

使用Python绘制QQ图并分析数据 在这篇博客中&#xff0c;我们将探讨如何使用Python中的pandas库和matplotlib库来绘制QQ图&#xff08;Quantile-Quantile Plot&#xff09;&#xff0c;并分析数据文件中的内容。QQ图是一种常用的统计图表&#xff0c;用于检查一组数据是否服从…

A股探底回升,2900点强势支撑,3000点还会远吗?

今天的A股探底回升&#xff0c;姿势绝了&#xff01;具体原因是这样的&#xff0c;盘面上出现2个重要变化&#xff0c;一起来看看&#xff1a; 1、今天两市低开高走&#xff0c;一度回踩2904点&#xff0c;然后筑底反弹&#xff0c;究竟是昙花一现还是迎来大反击&#xff1f; …

深度学习-数学基础(四)

深度学习数学基础 数学基础线性代数-标量和向量线性代数-向量运算向量加和向量内积向量夹角余弦值 线性代数-矩阵矩阵加法矩阵乘法矩阵点乘矩阵计算的其他内容 人工智能-矩阵的操作矩阵转置&#xff08;transpose&#xff09;矩阵与向量的转化 线性代数-张量&#xff08;tensor…

1001-04SF 同轴连接器

型号简介 1001-04SF是Southwest Microwave的2.92 mm连接器。该连接器采用多种材料制成&#xff0c;包括不锈钢、黄铜和硅橡胶等&#xff0c;以确保其性能和耐用性。它可以帮助工程师和制造商进行设计和制造&#xff0c;并确保连接器的性能和质量。 型号特点 电缆螺母&#xff…

【中项第三版】系统集成项目管理工程师 | 第 4 章 信息系统架构④ | 4.7

前言 第4章对应的内容选择题和案例分析都会进行考查&#xff0c;这一章节属于技术相关的内容&#xff0c;学习要以教材为准。本章分值预计在4-5分。 目录 4.7 安全架构 4.7.1 安全威胁 4.7.2 定义与范围 4.7.3 整体架构设计 4.7.4 网络安全架构设计 4.7.5 数据库系统安…

【Python基础】代码如何打包成exe可执行文件

本文收录于 《一起学Python趣味编程》专栏&#xff0c;从零基础开始&#xff0c;分享一些Python编程知识&#xff0c;欢迎关注&#xff0c;谢谢&#xff01; 文章目录 一、前言二、安装PyInstaller三、使用PyInstaller打包四、验证打包是否成功五、总结 一、前言 本文介绍如何…

西安电子科技大学811/821/833/834/953小班(8人)授课上线

你还在为自己努力&#xff0c;而成绩上不去而烦恼嘛&#xff1f;来看看下面这个小班介绍&#xff0c;或许你会感兴趣&#xff01; 01、西电研梦现在开设一个8人小班&#xff0c;什么是8人小班授课呢&#xff1f; 小班采用82授课机制&#xff1b;其中8人指&#xff1a;8位考研…

活动回顾|2024 MongoDB Developer Day圆满收官!

上周六&#xff0c;MongoDB专家与团队在深圳 与90位开发者度过了充实一日 至此&#xff0c;2024 MongoDB Developer Day 北上深三站之行全部圆满结束&#xff01; 一文回顾本次活动全程与精彩影像&#xff01; MongoDB Developer Day 专为开发者定制的技术盛宴 全天沉浸动手实…