基于ant-design-vue3多功能操作表格,表头序号为动态添加记录按钮,鼠标在表格记录行,当前行序号显示删除按钮

由于项目需要,并考虑到尽可能让空间利用率高,因此定制开发一个表格组件,组件功能主要是在序号表头位置为添加按钮,点击按钮,新增一行表格数据;表格数据删除不同于以往表格在操作栏定义删除按钮,该组件删除按钮在表格每行记录数据序号位置处,每当鼠标停留在当前记录行时,显示删除按钮。效果图如下所示:
在这里插入图片描述
鼠标在表格记录行外效果图:
在这里插入图片描述
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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值