由于项目需要,并考虑到尽可能让空间利用率高,因此定制开发一个表格组件,组件功能主要是在序号表头位置为添加按钮,点击按钮,新增一行表格数据;表格数据删除不同于以往表格在操作栏定义删除按钮,该组件删除按钮在表格每行记录数据序号位置处,每当鼠标停留在当前记录行时,显示删除按钮。效果图如下所示:
鼠标在表格记录行外效果图:
1.实现代码
表头json数据
export const mergeHeaderSchema: BasicColumn[] = [
{
title: 'ID',
dataIndex: 'index',
key: 'index',
align: 'center',
},
{
title: '权属',
key: 'ownership',
dataIndex: 'ownership',
align: 'center',
width: '12%',
},
{
title: '起源',
key: 'origin',
dataIndex: 'origin',
align: 'center',
width: '12%',
},
{
title: '商品林',
dataIndex: 'name1',
align: 'center',
children: [
{
title: '合计',
key: 'total',
dataIndex: 'total',
align: 'center',
width: '8%',
},
{
title: '主伐',
key: 'no',
edit: true,
dataIndex: 'name3',
align: 'center',
},
{
title: '抚育采伐',
key: 'address',
dataIndex: 'name4',
align: 'center',
},
{
title: '低产林改造',
key: 'no',
dataIndex: 'name5',
align: 'center',
},
{
title: '其他采伐',
key: 'no',
dataIndex: 'name6',
align: 'center',
},
],
},
{
title: '公益林',
dataIndex: 'name7',
align: 'center',
children: [
{
title: '合计',
key: 'total',
dataIndex: 'total',
align: 'center',
width: '8%',
},
{
title: '更新采伐',
key: 'no',
dataIndex: 'name9',
align: 'center',
},
{
title: '抚育采伐',
key: 'address',
dataIndex: 'name10',
align: 'center',
},
{
title: '低效林改造',
key: 'no',
dataIndex: 'name11',
align: 'center',
},
{
title: '其他采伐',
key: 'no',
dataIndex: 'name12',
align: 'center',
},
],
},
];
<template>
<a-spin :spinning="confirmLoading">
<a-form ref="formRef" class="antd-modal-form" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-row>
<a-col :span="12">
<a-form-item label="规划">
<a-select v-model:value="formData.period">
<a-select-option :value="135">135</a-select-option>
<a-select-option :value="145">145</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="年份">
<a-select v-model:value="formData.period">
<a-select-option :value="2024">2024</a-select-option>
<a-select-option :value="2023">2023</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="年文号">
<a-select v-model:value="formData.period">
<a-select-option :value="1">1</a-select-option>
<a-select-option :value="2">2</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="编限单位">
<Cascader v-bind="attrs" :value="state" :options="options" />
</a-form-item>
</a-col>
<!-- 重点代码 -->
<a-col :span="24">
<a-table bordered :columns="mergeHeaderSchema" :data-source="data" class="plus-common-table" :customRow="rowClick" :pagination="false">
<!-- 重点代码,插槽定义,表头序号为动态添加记录按钮 -->
<template #headerCell="{ column }">
<template v-if="column.dataIndex === 'index'">
<Icon icon="ant-design:plus-circle-outlined" class="plus-add" @click="handleAddClick" />
</template>
<template v-else-if="column.dataIndex === 'ownership' || column.dataIndex === 'origin'">
<div class="table-required">
<span>{{ column.title }}(</span>
<span class="required-star">*</span>
<span>)</span>
</div>
</template>
</template>
<!-- 重点代码,插槽定义,鼠标在表格记录行,当前行序号显示删除按钮 -->
<template #bodyCell="{ record, index, column }">
<template v-if="column.dataIndex === 'index'">
<div class="table-index">
{{ index + 1 }}
</div>
<Icon v-if="showDeleteBtn[index]" icon="ant-design:close-circle" class="plus-delete-btn" @click="handleDeleteClick(record)" />
</template>
<template v-else-if="column.dataIndex === 'ownership'">
<a-select v-model:value="formData.period" class="table-select">
<a-select-option :value="2024">2024</a-select-option>
<a-select-option :value="2023">2023</a-select-option>
</a-select>
</template>
<template v-else-if="column.dataIndex === 'origin'">
<a-select v-model:value="formData.period" class="table-select">
<a-select-option :value="2024">2024</a-select-option>
<a-select-option :value="2023">2023</a-select-option>
</a-select>
</template>
<template v-else-if="column.dataIndex === 'total'">
<div class="table-total">{{ record.total }}</div>
</template>
<template v-else>
<a-input @blur="handleEditFinish(record, column)" @input="handleInput($event, column)" />
</template>
</template>
</a-table>
</a-col>
</a-row>
</a-form>
</a-spin>
</template>
<script lang="ts" setup>
import { ref, reactive, defineExpose, nextTick, defineProps, computed, onMounted, onUnmounted } from 'vue';
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
import { getValueType } from '/@/utils';
import { saveOrUpdate } from '../Quota.api';
import { Cascader, Form, message } from 'ant-design-vue';
import { BasicTable, useTable } from '@/components/Table';
import { mergeHeaderSchema } from '../Quota.data';
let timerId: any;
const handleInput = (event, column) => {
const regex = /^(\d+(\.\d*)?)$/;
const valid = regex.test(event.target.value);
if (!valid) {
message.error('【' + column.title + '】格式有误,请输入数字');
}
};
const props = defineProps({
formDisabled: { type: Boolean, default: false },
formData: { type: Object, default: () => {} },
formBpm: { type: Boolean, default: true },
});
const formRef = ref();
const showDeleteBtn = ref([false, false, false, false, false]);
const useForm = Form.useForm;
const emit = defineEmits(['register', 'ok']);
const formData = reactive<Record<string, any>>({
id: '',
businessCode: '',
period: '',
referenceCode: '',
year: undefined,
ownership: '',
type: '',
origin: '',
province: '',
city: '',
county: '',
delFlag: undefined,
town: '',
village: '',
limitingUnit: '',
status: '',
});
const { createMessage } = useMessage();
const labelCol = ref<any>({ xs: { span: 24 }, sm: { span: 5 } });
const wrapperCol = ref<any>({ xs: { span: 24 }, sm: { span: 16 } });
const confirmLoading = ref<boolean>(false);
//表单验证
const validatorRules = {};
const { resetFields, validate, validateInfos } = useForm(formData, validatorRules, { immediate: true });
const attrs = reactive<Record<string, any>>({});
const state = ref();
const [registerTable] = useTable({
title: '多级表头示例',
// api: demoListApi,
columns: mergeHeaderSchema,
});
const data = ref<Record<string, any>>([]);
const options = [
{
value: 'zhejiang',
label: 'Zhejiang',
children: [
{
value: 'hangzhou',
label: 'Hangzhou',
children: [
{
value: 'xihu',
label: 'West Lake',
},
],
},
],
},
{
value: 'jiangsu',
label: 'Jiangsu',
children: [
{
value: 'nanjing',
label: 'Nanjing',
children: [
{
value: 'zhonghuamen',
label: 'Zhong Hua Men',
},
],
},
],
},
];
// 表单禁用
const disabled = computed(() => {
if (props.formBpm === true) {
if (props.formData.disabled === false) {
return false;
} else {
return true;
}
}
return props.formDisabled;
});
function handleAddClick() {
data.value.push({ index: Date.now(), total: 200 });
}
function handleDeleteClick(record) {
data.value = data.value.filter((item) => item.index !== record.index);
}
function rowClick(record, index) {
return {
// onClick: (event) => {
// console.info(record, index);
// },
// onMouseenter: (event) => {
// // 清除之前的定时器(如果存在)
// clearTimeout(timerId);
// // 设置一个新的定时器,等待一段时间后执行
// timerId = setTimeout(() => {
// // 鼠标停止移动后的操作
// console.log('Mouse has stopped moving over the element');
// showDeleteBtn.value[index] = true;
// }, 200);
// },
onMouseleave: (event) => {
showDeleteBtn.value.forEach((item, index) => {
showDeleteBtn.value[index] = false;
});
// 清除定时器
clearTimeout(timerId);
// showDeleteBtn.value[index] = false;
},
onMousemove: (event) => {
// 如果鼠标在元素内移动,清除之前的定时器并重新设置
clearTimeout(timerId);
timerId = setTimeout(() => {
showDeleteBtn.value[index] = true;
// ... 同样的操作
}, 50);
// showDeleteBtn.value[index] = false;
},
};
}
// 在组件卸载前移除事件监听器(可选,但建议这样做以避免内存泄漏)
onUnmounted(() => {
// 清除定时器
clearTimeout(timerId);
// 如果需要,可以在这里移除其他事件监听器
});
function handleEditFinish(record, column) {}
/**
* 新增
*/
function add() {
edit({});
}
/**
* 编辑
*/
function edit(record) {
nextTick(() => {
resetFields();
//赋值
Object.assign(formData, record);
});
}
/**
* 提交数据
*/
async function submitForm() {
// 触发表单验证
await validate();
confirmLoading.value = true;
const isUpdate = ref<boolean>(false);
//时间格式化
let model = formData;
if (model.id) {
isUpdate.value = true;
}
//循环数据
for (let data in model) {
//如果该数据是数组并且是字符串类型
if (model[data] instanceof Array) {
let valueType = getValueType(formRef.value.getProps, data);
//如果是字符串类型的需要变成以逗号分割的字符串
if (valueType === 'string') {
model[data] = model[data].join(',');
}
}
}
await saveOrUpdate(model, isUpdate.value)
.then((res) => {
if (res.success) {
createMessage.success(res.message);
emit('ok');
} else {
createMessage.warning(res.message);
}
})
.finally(() => {
confirmLoading.value = false;
});
}
defineExpose({
add,
edit,
submitForm,
});
</script>
<style lang="less" scoped>
.antd-modal-form {
height: 500px !important;
overflow-y: auto;
padding: 14px;
}
::v-deep(.ant-table-content) {
.table-required {
display: flex;
align-items: center;
justify-content: center; /* 水平居中 */
display: -webkit-flex;
}
.required-star {
color: red;
font-size: 20px;
}
.ant-table-cell .plus-add {
font-size: 30px !important;
color: #1890ff;
}
.ant-table-cell {
padding: 0;
margin: 0;
height: 35px;
}
.table-select {
margin: 0;
padding: 0;
width: 100%;
}
.ant-input {
border: 0;
height: 100%;
padding: 5px;
}
.table-total {
height: 100%;
display: flex;
align-items: center;
justify-content: center; /* 水平居中 */
background-color: lightgray;
}
.plus-delete-btn {
font-size: 30px !important;
color: white;
background-color: #f56c6c;
position: absolute;
border-radius: 15px;
top: 50%; /* 将元素的顶部移动到父元素的中心 */
left: 50%; /* 将元素的左边移动到父元素的中心 */
transform: translate(-50%, -50%); /* 使用转换移动元素自身的50%宽度和高度,使其真正居中 */
}
.table-index {
width: 100% !important;
height: 100% !important;
display: flex;
align-items: center;
justify-content: center; /* 水平居中 */
//text-align: center;
}
}
.element {
/* 初始样式 */
background-color: lightblue;
padding: 10px;
cursor: pointer;
}
.element:hover {
/* 鼠标悬停时的样式 */
background-color: lightgreen;
}
</style>
2.重点代码标识
表头序号为动态添加记录按钮
3.相关大数据学习demo地址:
https://github.com/carteryh/big-data