Handsontable纯前端类似excel的在线表格(vue3.0版本)

Handsontable官网 Options API reference - JavaScript Data Grid | Handsontable

使用Handsontable插件,下拉累计叠加功能

HotTableDialog.vue组件

<template>
  <el-dialog
    v-model="visible"
    title="批量编辑"
    width="630px"
    :close-on-click-modal="false"
    append-to-body
    :lock-scroll="false"
  >
    <my-hot-table
    v-if="visible"
      width="100%"
      height="auto"
      ref="myHotTableComponentRef"
      :solutionId="solutionId"
      @change-data="submitFormOk"
    ></my-hot-table>
    <template #footer>
      <el-button @click="handleCancel">取 消</el-button>
      <el-button type="primary" @click="submitForm">确 定</el-button>
    </template>
  </el-dialog>
</template>

<script lang="ts">
import myHotTable from "./myHotTable.vue";

import {
  ref,
  reactive,
  onMounted,
  toRefs,
  getCurrentInstance,
  computed,
  nextTick,
} from "vue";

export default {
  props: {
    visible: {
      type: Boolean,
      default: false,
    },
    id: {
      type: String,
      default: "",
    },
    solutionId: {
      type: String,
      default: "",
    },
    data: {
      type: Array,
      default: () => {
        return [];
      },
    },
  },
  components: { myHotTable },
  emits: ["update:visible", "submit-ok"],
  setup(props, ctx) {
    const { proxy }: any = getCurrentInstance();
    const myHotTableComponentRef = ref<any>(null);
    const visible = computed({
      get: () => {
        if (props.visible) {
          nextTick(()=>{
            myHotTableComponentRef.value.getList();

        })
        }
        return props.visible;
      },
      set: (val) => {
        ctx.emit("update:visible", val);
      },
    });
    onMounted(() => {
      
    });

    const handleCancel = () => {
      visible.value = false;
    };

    const submitForm = () => {
      myHotTableComponentRef.value.submitForm();
    };
    const submitFormOk=()=>{
        ctx.emit('submit-ok')

    }
    return {
      submitForm,
      submitFormOk,
      handleCancel,
      visible,
      myHotTableComponentRef,
    };
  },
};
</script>

myHotTable组件

<template>
  <hot-table
    width="100%"
    height="auto"
    :settings="hotSettings"
    :minRows="tableData.length"
    ref="hotTableComponent"
  ></hot-table>
</template>

<script lang="ts">
import { HotTable } from '@handsontable/vue3';
import 'handsontable/dist/handsontable.full.css'; //表格样式
import { registerAllModules } from 'handsontable/registry'; // 在线编辑样式
import 'handsontable/languages/zh-CN'; //汉语包
import { cloneDeep } from 'lodash';
import { useHotTable } from './hotTableData';

registerAllModules();
import {
  ref,
  reactive,
  onMounted,
  toRefs,
  getCurrentInstance,
  computed,
  nextTick,
} from "vue";
import { ElMessage } from "element-plus";



export default {
  props: {
    id: {
      type: String,
      default: '',
    },
    solutionId: {
      type: String,
      default: "",
    },
    data: {
      type: Array,
      default: () => {
        return [];
      },
    },
  },
  components: { HotTable },
  emits: ['update:visible', 'change-data'],
  setup(props, ctx) {
    const { proxy }: any = getCurrentInstance();
    const hotTableComponent = ref<any>(null);
    const tableData = ref<Array<any>>([]);
    const { afterChange, afterSelectionByProp, afterSelectionEndByProp } =
      useHotTable(hotTableComponent);
    const hotSettings = ref({
      language: 'zh-CN', // 官方汉化
      licenseKey: 'non-commercial-and-evaluation', //去除底部非商用声明
      colHeaders: ['名称', '点属性', '高(米)', '类型'],
      columns: [
        {
          data: 'name',
          readOnly: false,
          type: 'text',
          dateFormat: null,
          source: null,
          strict: false,
          numericFormat: null,
        },
        {
          data: 'typeText',
          readOnly: false,
          type: 'dropdown',
          dateFormat: null,
          source: ["转角", "直角"],
          // editor: 'select',
          // selectOptions: ["转角", "直角"],
          strict: true,
          numericFormat: null,
        },
        {
          data: 'height',
          readOnly: false,
          type: 'numeric',
          dateFormat: null,
          source: null,
          strict: false,
          numericFormat: null,
        },
        {
          data: 'insulatorType',
          readOnly: false,
          type: 'text',
          dateFormat: null,
          source: null,
          strict: true,
          numericFormat: null,
        },
      ],

      data: [],
      currentRowClassName: 'currentRow', // 突出显示行
      currentColClassName: 'currentCol', // 突出显示列
      minSpareRows: 0, //行留白
      autoWrapRow: false, //自动换行

      trimWhitespace: false, //去除空格
      rowHeaderWidth: 100, //单元格宽度
      stretchH: 'all',
      renderAllRows: false,
      rowHeaders: true, // 行标题   布尔值 / 数组/  函数
      formulas: false, //公式
      copyable: true, // 允许键盘复制
      wordWrap: false, // 自动换行
      copyPaste: true, //复制粘贴
      filters: false, //允许通过内置组件或API过滤表数据
      search: false,
      fixedColumnsLeft: 0, // 固定左边列数
      fixedRowsTop: 0, // 固定上边列数
      columnSorting: true, // 排序
      contextMenu: false, //右键菜单
      afterChange: afterChange,
      afterSelectionByProp: afterSelectionByProp,
      afterSelectionEndByProp: afterSelectionEndByProp,
    });

    onMounted(() => {
      getList();
    });
    const getList = () => {
      if (!props.solutionId) return;
 
      getTowersList(props.solutionId)
        .then((res) => {
          loading.close();
          let data = cloneDeep(res.data);
          data.map((item: any) => {
            item.typeText = item.type == 0 ? "转角" : "直线";
          });
          tableData.value = data;
          nextTick(() => {
            hotTableComponent.value?.hotInstance.loadData(tableData.value);
          });
        })
        .catch((e) => loading.close());
    };
    const submitForm = () => {
      let data = hotTableComponent.value.hotInstance.getSourceData();
      console.log(">>>>>>>>",data);
      
      let newTowerList = cloneDeep(data);
      newTowerList.map((item: any) => {
        item.type = item.typeText == "转角" ? 0 : 1;
        delete item.typeText;
      });

      console.log(newTowerList);
      if (!newTowerList.length) {
        ElMessage.warning("数据不能为空");
        return;
      }
   
    };
    return {
      submitForm,
      hotSettings,
      tableData,
      hotTableComponent,
    };
  },
};
</script>
<style>
.htContextMenu:not(.htGhostTable) {
  z-index: 9999999 !important;
}
</style>

hotTableData.ts

import { cloneDeep } from "lodash";
import { Ref, ref, ToRef } from "vue";
export const useHotTable = (hot: any) => {
  let tableList: { name: any }[] = []; //获取的初始状态,只得到一次
  let autofillTarget = false;
  /**
   * 更改触发
   * @param changeData
   * @param source
   */
  const afterChange = (changeData: any, source: any) => {
    if (source == "Autofill.fill") {
      autofillTarget = true;
    } else {
      autofillTarget = false;
    }
  };
  /**
   * 获取拖动放下记录下来
   * @param r 选中的起始纵坐标
   * @param p 选中的结束的纵坐标
   * @param r2 p为起始的属性
   * @param p2 结束的属性
   * @returns
   */
  const afterSelectionByProp = (r: any, p: any, r2: any, p2: any) => {
    console.log(">>>>>>>>>r", r);
    console.log(">>>>>>>>>r2", r2);
    tableList = [];
    if (p != p2 || p != "name") {
      //横向拖动表格不做任何效果,且只有名称需要递增效果
      autofillTarget = false;
      return false;
    }

    if (r == r2) {
      //如果只点击一个格子,不做任何操作
      autofillTarget = false;
      return false;
    }

    tableList = cloneDeep(hot.value.hotInstance.getSourceData()); //获取表格当前的数据
    console.log(">>>>>>>>>>>>", tableList);
    let name = hot.value.hotInstance.getSourceData()[r].name; //当前单元格中的箱号值
    for (let j = r; j < r2; j++) {
      //循环选中单元格
      name = tableList[j].name ? tableList[j].name : name;
      let numList = name.match(/\d+(.\d+)?/g); //提取字符串的所有数字,得到数字数组  // ["2.75","3.65"]
      if (numList) {
        let lastNumber = numList[numList.length - 1]; //获取最后一个连续的数字
        let lastIndex = name.lastIndexOf(lastNumber); //查找最后一个数字在name中的下标位置
        let newNumber = lastNumber * 1 + 1; //数字叠加
        let newStr =
        name.substring(0, lastIndex) +
          newNumber +
          name.substring(lastNumber.length + lastIndex, name.length);
        console.log(">>>>>>>>>>新数值", newStr);
        tableList[j * 1 + 1].name = newStr;
      }
    }
  };
  /**
   * 在选择一个或多个单元格后激发(例如鼠标悬停时)。此钩子函数为了获取拖动放下记录下来
   * @param index
   * @param direction
   * @param data
   * @param baseRange
   */
  const afterSelectionEndByProp = (
    index: any,
    direction: any,
    data: any,
    baseRange: any
  ) => {
    if (autofillTarget && tableList.length > 0) {
      hot.value.hotInstance.loadData(tableList);
      autofillTarget = false;
    }
  };

  return {
    afterChange,
    afterSelectionEndByProp,
    afterSelectionByProp,
  };
};

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

瘦瘦瘦大人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值