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

news2024/9/9 4:58:54

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度学习(概念相关)

深度学习&#xff08;论文相关&#xff09; 深度学习一些概念 通过阅读论文可以理解提取数据特征的重要性 深度学习学习怎么去提取特征 应用领域 深度学习应用&#xff1a; 输入&#xff1a;图像输入或者文字输入 算法&#xff1a;还是基础的模块计算 神经网络中的参数几千…

[JavaScript] 动态获取方法参数名

JavaScript&#xff08;简称“JS”&#xff09;是一种具有函数优先的轻量级&#xff0c;解释型或即时编译型的编程语言。虽然它是作为开发Web页面的脚本语言而出名&#xff0c;但是它也被用到了很多非浏览器环境中&#xff0c;JavaScript基于原型编程、多范式的动态脚本语言&am…

Java导出Excel给每一列设置不同样式示例

Excel导出这里不讲&#xff0c;方法很多&#xff0c;原生的POI可以参照 Java原生POI实现的Excel导入导出&#xff08;简单易懂&#xff09; 这里只说怎么给Excel每一列设置不同的样式&#xff0c;比如下面这样的 直接上代码 Overridepublic void exportTemplate(HttpServletRe…

昇思25天学习打卡营第23天|CV-ResNet50迁移学习

打卡 目录 打卡 迁移学习 实战练习 数据准备 数据处理 数据处理函数 数据处理流程 数据可视化 训练模型 构建Resnet50网络 固定特征进行训练 network 的输出 训练和评估 可视化模型预测 通过本文&#xff0c;了解迁移学习的重点在于&#xff0c;了解你的模型结构…

LGA-4500激光气体分析仪说明手册

目 录 阅 读 说 明 I 用户须知 I 概况 I 注意和警示信息 I 供货和运输 III 质保和维修 III 联系方式 III 一、简介 1 1.1概要 1 1.2测量原理 1 1.2.1单线光谱技术 1 1.2.2激光频率扫描技术 2 1.2.3谱线展宽自动修正技术 2 1.3系统组成 2 1.4系统特点 3 1.5系统指标 4 1.6激光产…

决策树可解释性分析

决策树可解释性分析 决策树是一种广泛使用的机器学习算法&#xff0c;以其直观的结构和可解释性而闻名。在许多应用场景中&#xff0c;尤其是金融、医疗等领域&#xff0c;模型的可解释性至关重要。本文将从决策路径、节点信息、特征重要性等多个方面分析决策树的可解释性&…

Thymeleaf(看这一篇就够了)

目录&#xff1a; Thymeleaf变量输出操作字符串&时间条件判断迭代遍历使用状态变量获取域中的数据URL写法相关配置 Thymeleaf Thymeleaf是一款用于渲染XML/HTML5内容的模板引擎&#xff0c;类似JSP。它可以轻易的与SpringMVC等Web框架进行集成作为Web应用的模板引擎。Spri…

暄桐林曦老师推荐书单——《菜根谭》

暄桐是一间传统美学教育教室&#xff0c;创办于2011年&#xff0c;林曦是创办人和授课老师&#xff0c;教授以书法为主的传统文化和技艺&#xff0c;皆在以书法为起点&#xff0c;亲近中国传统之美&#xff0c;以实践和所得&#xff0c;滋养当下生活。      在暄桐教室的课…

机械运动、工控机概念整理

Signal &#xff1a; 信号 pul 【pulse】是驱动脉冲信号&#xff0c;发一个脉冲&#xff0c;电机走一步。 dir是方向控制信号&#xff0c;控制电机正转或反转。 ena是使能信号&#xff0c;这个信号必须开启&#xff0c;才能驱动电机。 voltage&#xff1a; 电压 VCC&#…

Python软件开发:AI毕业设计生成器引领未来

&#x1f31f; 革新软件开发&#xff1a;Python毕业设计生成器引领未来 &#x1f680; 目录 &#x1f31f; 革新软件开发&#xff1a;Python毕业设计生成器引领未来 &#x1f680;&#x1f393; 课题简介&#x1f31f; 开发目的&#x1f4c8; 开发意义 &#x1f4da; 研究方法&…

[ BLE4.0 ] 伦茨ST17H66开发-串口UART0的接收与发送

目录 一、前言 二、实现步骤 1.设置回调函数 2.关闭睡眠模式 三、效果展示 四、工程源代码 一、前言 串口通信在任何一款单片机开发中都是尤为重要的。本文涉及的开发所使用的例程依然是基于[ BLE4.0 ] 伦茨ST17H66开发-OSAL系统中添加自己的Task任务文章的工程源码&#x…

进程间通信--套接字socket

前面提到的管道、消息队列、共享内存、信号和信号量都是在同一台主机上进行进程间通信&#xff0c;那要想跨网络与不同主机上的进程之间通信&#xff0c;就需要Socket通信了。 实际上&#xff0c;Socket通信不仅可以跨网络与不同主机的进程间通信&#xff0c;还可以在同主机上…

一键升级GIS场景视觉效果,告别繁琐操作

在当今的数字化时代&#xff0c;GIS&#xff08;地理信息系统&#xff09;不再仅仅只能通过一些二维示意图或简陋的三维地形图表示&#xff0c;它可以通过专业的软件简单升级视效。想象一下&#xff0c;在你的GIS场景中&#xff0c;阳光明媚的天气、突如其来的暴风雨、缭绕的晨…

工时管理平台核心功能解析:你需要了解的

国内外主流的10款工时管理平台对比&#xff1a;PingCode、Worktile、Todoist、ClickUp、滴答清单、专注清单、一木清单、NarTick、Tweek、朝暮计划。 在选择合适的工时管理平台时&#xff0c;你是否感到挑战重重&#xff1f;市场上的各种选项似乎都声称能够提升效率和减轻管理负…

【SQL Server点滴积累】SQL Server 2016数据库邮件(Database Mail)功能故障的解决方法

今天和大家分享SQL Server 2016数据库邮件(Database Mail)功能故障的解决方法 故障现象&#xff1a; 在SQL Server 2016中配置完成数据库邮件(Database Mail)功能后&#xff0c;当你尝试发送测试邮件后&#xff0c;既收不到测试邮件&#xff0c;也不显示错误消息 KB3186435 -…

Python数值计算(11)——拉格朗日插值

本篇介绍一下多项式插值中&#xff0c;拉格朗日法的原理及其实现。 1. 一点数学知识 先引用数学背景。如果给定N个点&#xff0c;然后要求一个多项式通过这N个点&#xff0c;最简单直接的方式是列出线性方程求解&#xff0c;N个点可以确定N个未知量&#xff0c;则所求的拟合多…

下面关于枚举的描述正确的一项是?

A. 枚举中定义的每一个枚举项其类型都是String&#xff1b; B. 在Java中可以直接继承java.util.Enum类实现枚举类的定义&#xff1b; C. 利用枚举类中的values()方法可以取得全部的枚举项&#xff1b; D. 枚举中定义的构造方法只能够使用private权限声明&#xff1b; 答案选择…

springboot山东外事职业大学校园食堂点餐系统-计算机毕业设计源码10417

摘 要 近年来&#xff0c;随着国民收入的提高&#xff0c;各行业取得长足进步&#xff0c;也带动了互联网行业的快速发展&#xff0c;许多传统行业开始与互联网相结合&#xff0c;通过数字化转型打造新的发展生态。 本文针对山东外事大学校园食堂点餐系统的需求&#xff0c;基于…

Java内存区域与内存溢出异常详解

在Java编程中&#xff0c;理解Java虚拟机的内存布局及其管理机制对于开发高效、稳定的应用程序至关重要。Java虚拟机的内存主要分为几个运行时区域&#xff0c;这些区域各司其职&#xff0c;共同支撑起Java程序的运行。本文将详细探讨Java虚拟机的内存区域以及这些区域如何与内…

Yolov模型的使用及数据集准备(1)LabelImg的下载和使用

1、LabelImg下载&#xff1a; labelimg简单来说就是打标签用的软件&#xff0c;当需要使用自定义数据集进行模型训练时&#xff0c;往往需要使用该软件来打标签。 下载地址&#xff1a;GitHub - HumanSignal/labelImg 1.1下载之后对压缩包进行解压 2、打开电脑的anaconda pro…