| | |
| | | <template> |
| | | <el-table v-loading="loading" :data="tableData" :border="border" :show-selection="showSelection" :height="height" :max-height="maxHeight" |
| | | :header-cell-style="{ background: '#EBEEF5', color: '#3D3D3D' }" @selection-change="handleSelectionChange" |
| | | @row-click="handleRowClick" @row-dblclick="handleRowDblClick" :row-class-name="tableRowClassName" @cell-click="handleCellClick" :max-width="maxWidth" |
| | | @export="handleExport"> |
| | | <el-table-column v-if="showSelection" type="selection" width="55" align="center" /> |
| | | <el-table-column v-if="showIndex" label="序号" type="index" width="60" align="center" /> <template |
| | | v-for="col in columns" :key="col.prop"> |
| | | <el-table-column v-bind="col" :show-overflow-tooltip="shouldShowTooltip(col, tableData)" align="center"> <template |
| | | #default="scope"> |
| | | <el-table |
| | | v-loading="loading" |
| | | :data="tableData" |
| | | :border="border" |
| | | :show-selection="showSelection" |
| | | :height="height" |
| | | :max-height="maxHeight" |
| | | :header-cell-style="{ background: '#EBEEF5', color: '#3D3D3D' }" |
| | | @selection-change="handleSelectionChange" |
| | | @row-click="handleRowClick" |
| | | @row-dblclick="handleRowDblClick" |
| | | :row-class-name="tableRowClassName" |
| | | @cell-click="handleCellClick" |
| | | :max-width="maxWidth" |
| | | @export="handleExport" |
| | | :show-overflow-tooltip="showOverflowTooltip" |
| | | > |
| | | <el-table-column |
| | | v-if="showSelection" |
| | | type="selection" |
| | | width="55" |
| | | align="center" |
| | | /> |
| | | <el-table-column |
| | | v-if="showIndex" |
| | | label="序号" |
| | | type="index" |
| | | width="60" |
| | | align="center" |
| | | /> |
| | | <template v-for="col in columns" :key="col.prop"> |
| | | <el-table-column |
| | | v-bind="col" |
| | | :show-overflow-tooltip="false" |
| | | align="center" |
| | | > |
| | | <template #default="scope"> |
| | | <template v-if="col.slot"> |
| | | <slot></slot> |
| | | </template> |
| | | <template v-else> |
| | | <div class="cell-edit" @dblclick="handleCellEdit(scope.row, col.prop)" |
| | | :class="{'editable': isColumnEditable(col.prop)}"> <span |
| | | v-if="!scope.row.editing || !scope.row.editing[col.prop]" class="cell-text"> |
| | | {{ scope.row[col.prop] == null || scope.row[col.prop] === '' ? '--' : scope.row[col.prop] }} |
| | | <div |
| | | class="cell-edit" |
| | | @dblclick="handleCellEdit(scope.row, col.prop)" |
| | | :class="{ editable: isColumnEditable(col.prop) }" |
| | | > |
| | | <span |
| | | v-if="!scope.row.editing || !scope.row.editing[col.prop]" |
| | | class="cell-text" |
| | | > |
| | | {{ |
| | | scope.row[col.prop] == null || scope.row[col.prop] === "" |
| | | ? "--" |
| | | : scope.row[col.prop] |
| | | }} |
| | | </span> |
| | | <el-input v-else v-model="scope.row[col.prop]" size="small" |
| | | @focus="handleCellFocus(scope.row, col.prop, $event)" @blur="handleCellSave(scope.row, col.prop)" |
| | | @keyup.enter="handleCellSave(scope.row, col.prop)" class="cell-input" /> |
| | | <el-input |
| | | v-else |
| | | v-model="scope.row[col.prop]" |
| | | size="small" |
| | | @focus="handleCellFocus(scope.row, col.prop, $event)" |
| | | @blur="handleCellSave(scope.row, col.prop)" |
| | | @keyup.enter="handleCellSave(scope.row, col.prop)" |
| | | class="cell-input" |
| | | /> |
| | | </div> |
| | | </template> |
| | | </template> |
| | | </el-table-column> |
| | | </template> |
| | | <!-- 操作列 --> |
| | | <el-table-column v-if="showOperations" :label="operationsLabel" :width="operationsWidth" fixed="right" align="center"> |
| | | <el-table-column |
| | | v-if="showOperations" |
| | | :label="operationsLabel" |
| | | :width="operationsWidth" |
| | | fixed="right" |
| | | align="center" |
| | | > |
| | | <template #default="scope"> |
| | | <slot name="operations" :row="scope.row"> |
| | | <el-button v-if="operations.includes('edit')" link type="primary" size="small" |
| | | @click="handleEdit(scope.row)">编辑</el-button> |
| | | <el-button |
| | | v-if="operations.includes('edit')" |
| | | link |
| | | type="primary" |
| | | size="small" |
| | | @click="handleEdit(scope.row)" |
| | | >编辑</el-button |
| | | > |
| | | <!-- <el-button--> |
| | | <!-- v-if="operations.includes('delete')"--> |
| | | <!-- link--> |
| | |
| | | </el-table-column> |
| | | </el-table> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { defineEmits, nextTick } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | |
| | | <script setup> |
| | | import { defineEmits, nextTick } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | const props = defineProps({ |
| | | // 获取行样式 |
| | | tableRowClassName: { |
| | | type: Function, |
| | | default: () => '' |
| | | default: () => "", |
| | | }, |
| | | // 获取高度 |
| | | height: { |
| | | type: [String, Number], |
| | | default: 'auto' |
| | | default: "auto", |
| | | }, |
| | | // 是否允许编辑单元格 |
| | | editableCells: { |
| | | type: Boolean, |
| | | default: true |
| | | default: true, |
| | | }, |
| | | // 指定可编辑的列,如果为空数组则表示所有列都不可编辑 |
| | | editableColumns: { |
| | | type: Array, |
| | | default: () => [] |
| | | default: () => [], |
| | | }, |
| | | // 最大宽度 |
| | | maxWidth: { |
| | | type: [String, Number], |
| | | default: 'auto' |
| | | default: "auto", |
| | | }, |
| | | handleCellClick: { |
| | | type: Function, |
| | | default: () => { } |
| | | default: () => {}, |
| | | }, |
| | | handleRowClick: { |
| | | type: Function, |
| | | default: () => { } |
| | | default: () => {}, |
| | | }, |
| | | handleExport: { |
| | | type: Function, |
| | | default: () => { } |
| | | default: () => {}, |
| | | }, |
| | | handleRowDblClick: { |
| | | type: Function, |
| | | default: () => { } |
| | | default: () => {}, |
| | | }, |
| | | // 高度 |
| | | maxHeight: { |
| | | type: [String, Number], |
| | | default: 'auto' |
| | | default: "auto", |
| | | }, |
| | | // 加载状态 |
| | | loading: { |
| | | type: Boolean, |
| | | default: false |
| | | default: false, |
| | | }, |
| | | // border |
| | | border: { |
| | | type: Boolean, |
| | | default: false |
| | | default: false, |
| | | }, |
| | | // 表格数据 |
| | | tableData: { |
| | | type: Array, |
| | | default: () => [] |
| | | default: () => [], |
| | | }, |
| | | // 是否显示选择列 |
| | | showSelection: { |
| | | type: Boolean, |
| | | default: false |
| | | default: false, |
| | | }, |
| | | // 是否显示序号列 |
| | | showIndex: { |
| | | type: Boolean, |
| | | default: true |
| | | default: true, |
| | | }, |
| | | // 列配置 |
| | | columns: { |
| | | type: Array, |
| | | default: () => [] |
| | | default: () => [], |
| | | }, |
| | | // 是否显示操作列 |
| | | showOperations: { |
| | | type: Boolean, |
| | | default: true |
| | | default: true, |
| | | }, |
| | | // 操作列标签 |
| | | operationsLabel: { |
| | | type: String, |
| | | default: '操作' |
| | | default: "操作", |
| | | }, |
| | | // 操作列宽度 |
| | | operationsWidth: { |
| | | type: [String, Number], |
| | | default: 100 |
| | | default: 100, |
| | | }, |
| | | // 显示哪些操作按钮 |
| | | operations: { |
| | | type: Array, |
| | | default: () => ['edit', 'delete', 'export'] |
| | | default: () => ["edit", "delete", "export"], |
| | | }, |
| | | // 删除确认信息 |
| | | deleteConfirmText: { |
| | | type: String, |
| | | default: '确认删除该记录?' |
| | | } |
| | | }) |
| | | default: "确认删除该记录?", |
| | | }, |
| | | showOverflowTooltip: { |
| | | type: Boolean, |
| | | default: true, |
| | | }, |
| | | }); |
| | | // 检查列是否需要显示tooltip |
| | | const shouldShowTooltip = (col, data) => { |
| | | // 如果没有prop,直接返回false |
| | | if (!col.prop) return false; |
| | | // 检查该列在所有数据中是否有非空值 |
| | | return data.some(row => row[col.prop] != null && row[col.prop] !== ''); |
| | | return data.some((row) => row[col.prop] != null && row[col.prop] !== ""); |
| | | }; |
| | | |
| | | // 处理单元格编辑 |
| | |
| | | if (!props.editableCells) return; |
| | | |
| | | // 如果指定了可编辑列,且当前列不在可编辑列中,则不允许编辑 |
| | | if (props.editableColumns.length > 0 && !props.editableColumns.includes(prop)) return; |
| | | if (props.editableColumns.length > 0 && !props.editableColumns.includes(prop)) |
| | | return; |
| | | |
| | | // 初始化editing对象 |
| | | if (!row.editing) { |
| | |
| | | |
| | | // 在下一个DOM更新周期,让输入框获得焦点并选中内容 |
| | | setTimeout(() => { |
| | | const inputElement = document.querySelector('.cell-edit .el-input__inner'); |
| | | const inputElement = document.querySelector(".cell-edit .el-input__inner"); |
| | | if (inputElement) { |
| | | inputElement.focus(); |
| | | inputElement.select(); |
| | | } |
| | | }, 10); |
| | | } |
| | | }; |
| | | |
| | | // 处理单元格保存 |
| | | const handleCellSave = (row, prop) => { |
| | | // 关闭编辑状态 |
| | | row.editing[prop] = false; |
| | | // 触发单元格编辑完成事件 |
| | | emit('cell-edit', row, prop, row[prop]); |
| | | } |
| | | emit("cell-edit", row, prop, row[prop]); |
| | | }; |
| | | // 处理单元格聚焦事件 |
| | | const handleCellFocus = (row, prop, event) => { |
| | | // 如果不允许编辑单元格,直接返回 |
| | | if (!props.editableCells) return; |
| | | |
| | | // 如果指定了可编辑列,且当前列不在可编辑列中,则不允许编辑 |
| | | if (props.editableColumns.length > 0 && !props.editableColumns.includes(prop)) return; |
| | | if (props.editableColumns.length > 0 && !props.editableColumns.includes(prop)) |
| | | return; |
| | | |
| | | // 初始化editing对象 |
| | | if (!row.editing) { |
| | |
| | | if (event && event.target) { |
| | | event.target.select(); |
| | | } |
| | | } |
| | | }; |
| | | // 判断列是否可编辑 |
| | | const isColumnEditable = (prop) => { |
| | | if (props.editableColumns.length === 0) { |
| | | return props.editableCells; |
| | | } |
| | | return props.editableColumns.includes(prop); |
| | | } |
| | | }; |
| | | |
| | | // 处理选择变化、编辑、删除和导出操作 |
| | | const emit = defineEmits(['selection-change', 'edit', 'delete', 'export', 'cell-edit']) |
| | | const emit = defineEmits([ |
| | | "selection-change", |
| | | "edit", |
| | | "delete", |
| | | "export", |
| | | "cell-edit", |
| | | ]); |
| | | const handleSelectionChange = (selection) => { |
| | | emit('selection-change', selection) |
| | | } |
| | | emit("selection-change", selection); |
| | | }; |
| | | const handleEdit = (row) => { |
| | | emit('edit', row) |
| | | } |
| | | emit("edit", row); |
| | | }; |
| | | const handleExport = (row) => { |
| | | emit('export', row) |
| | | } |
| | | </script> |
| | | <style scoped lang="scss"> |
| | | emit("export", row); |
| | | }; |
| | | </script> |
| | | <style scoped lang="scss"> |
| | | .el-table { |
| | | margin: 20px 0 !important; |
| | | } |
| | | |
| | | :deep(.el-table th) { |
| | | background-color: #f5f7fa; |
| | | } |
| | | |
| | | :deep(.cell-edit) { |
| | | margin: 20px 0 !important; |
| | | } |
| | | |
| | | :deep(.el-table th) { |
| | | background-color: #f5f7fa; |
| | | } |
| | | |
| | | :deep(.cell-edit) { |
| | | width: 100%; |
| | | height: 100%; |
| | | position: relative; |
| | | } |
| | | |
| | | :deep(.cell-edit .cell-text) { |
| | | width: 100%; |
| | | display: block; |
| | | } |
| | | |
| | | :deep(.cell-edit.editable:hover .cell-text) { |
| | | color: #409eff; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | :deep(.cell-input) { |
| | | width: 80%; |
| | | max-width: 120px; |
| | | min-width: 60px; |
| | | } |
| | | |
| | | :deep(.cell-input .el-input__inner) { |
| | | border-radius: 4px; |
| | | text-align: center; |
| | | transition: all 0.2s; |
| | | } |
| | | |
| | | /* 响应式样式 */ |
| | | @media screen and (max-width: 768px) { |
| | | :deep(.el-table) { |
| | | width: 100%; |
| | | height: 100%; |
| | | position: relative; |
| | | overflow-x: auto; |
| | | } |
| | | |
| | | :deep(.cell-edit .cell-text) { |
| | | width: 100%; |
| | | display: block; |
| | | } |
| | | |
| | | :deep(.cell-edit.editable:hover .cell-text) { |
| | | color: #409EFF; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | :deep(.cell-input) { |
| | | width: 80%; |
| | | max-width: 120px; |
| | | min-width: 60px; |
| | | } |
| | | |
| | | :deep(.cell-input .el-input__inner) { |
| | | border-radius: 4px; |
| | | text-align: center; |
| | | transition: all 0.2s; |
| | | } |
| | | |
| | | |
| | | /* 响应式样式 */ |
| | | @media screen and (max-width: 768px) { |
| | | :deep(.el-table) { |
| | | width: 100%; |
| | | overflow-x: auto; |
| | | } |
| | | } |
| | | </style> |
| | | } |
| | | </style> |