| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468 |
- <template>
- <div
- class="handsontable-container"
- :style="borderStyle"
- v-loading="loading"
- element-loading-background="rgba(245, 245, 245, 0.5)"
- >
- <div class="table-wrapper" ref="handsontableRef"></div>
- </div>
- </template>
- <script lang="ts">
- import {
- computed,
- defineComponent,
- onBeforeUnmount,
- onMounted,
- ref,
- toRefs,
- watch,
- } from "vue";
- import Handsontable from "@sc/handsontable";
- import { TreeNode } from "@sc/tree";
- import debounce from "lodash/debounce";
- import { off } from "@/utils/frontend/dom";
- import useClickPosition from "./composables/useClickPosition";
- import useInstance from "./composables/useInstance";
- import useTableSettings, {
- getColumnsSettings,
- getTableData,
- overwriteReadOnly,
- } from "./composables/useTableSettings";
- import "./renderer/registerRenderers";
- import { Hot } from "@/types/components/hot";
- // import './cell-types/registerCellTypes';
- export default defineComponent({
- name: "Handsontable",
- props: {
- // 表格的数据
- data: {
- type: Array,
- required: true,
- },
- // 表格的设置选项
- settings: {
- type: Object,
- required: true,
- },
- // 是否是树结构,即每行数据是否是 `TreeNode`
- tree: {
- type: Boolean,
- default: false,
- },
- // 边框, none 表示没有边框
- border: {
- type: String,
- default: "top,right,bottom,left",
- },
- // 整个表格是否只读
- readOnly: {
- type: Boolean,
- default: false,
- },
- // 是否处于 loading 状态
- loading: {
- type: Boolean,
- default: false,
- },
- },
- setup(props) {
- // 模板引用
- const handsontableRef = ref<HTMLElement | null>(null);
- const hotProps = props as Hot.IHandsontableProps;
- const tableSettings = useTableSettings(hotProps);
- let rawData = props.data as any;
- // Handsontable 实例
- let instance: Handsontable | null;
- type RowHeaders = boolean | ((visualRow: number) => number);
- const getRowHeader = (): RowHeaders => {
- if (tableSettings.rowHeaders !== undefined)
- return tableSettings.rowHeaders as any;
- return props.tree
- ? (visualRow: number) => {
- if (instance) {
- const rowSourceData = instance.getSourceDataAtRow(
- instance.toPhysicalRow(visualRow)
- ) as TreeNode;
- return rowSourceData.getCtx().row() + 1;
- }
- return visualRow;
- }
- : true;
- };
- // 重新渲染,供外界调用
- const Render = () => instance && instance.render();
- const onResize = debounce(() => Render(), 100);
- // 监听 mousedownPosition 事件的处理器
- let clickPositionHandler: any;
- onMounted(() => {
- const wrapperDOM = handsontableRef.value as HTMLElement;
- // handsontable 实例
- instance = useInstance(wrapperDOM, tableSettings);
- // 获取鼠标点击的区域相关代码
- clickPositionHandler = useClickPosition(wrapperDOM, tableSettings);
- // 设置行号
- (() => {
- const rowHeaders = getRowHeader();
- instance.updateSettings({ rowHeaders }, false);
- })();
- // 改变窗口大小监听事件
- window.addEventListener("resize", onResize);
- });
- const { readOnly, tree } = toRefs(props);
- // 重新加载数据,供外界调用
- const Load = (data?: any) => {
- if (data) rawData = data;
- instance && instance.loadData(getTableData(tree.value, rawData));
- };
- // 获取 instance,供外界调用
- const GetInstance = () => instance;
- // 更新 Settings,供外界调用(如增加或减少列)
- const Update = (newTableSettings: Hot.ISettings) => {
- const { columnsMeta } = newTableSettings;
- const columnsData = getColumnsSettings(columnsMeta);
- let newSettings;
- if (columnsData) {
- newSettings = { ...columnsData, ...newTableSettings };
- delete newSettings.columnsMeta;
- } else {
- newSettings = newTableSettings;
- }
- overwriteReadOnly(newSettings, readOnly.value);
- if (newSettings.rowHeaders === undefined)
- // 设置行号
- newSettings.rowHeaders = getRowHeader();
- instance && instance.updateSettings(newSettings, false);
- };
- watch(readOnly, () => {
- Update(props.settings);
- });
- watch(tree, () => {
- const newSettings = props.settings;
- const { columnsMeta } = newSettings;
- columnsMeta &&
- columnsMeta.forEach((columnMeta: any) => {
- if (columnMeta.renderer === "wc.switcherRenderer") {
- delete columnMeta.renderer;
- }
- });
- Update(newSettings);
- Load();
- });
- // 组件 unmount 之前销毁 handsontable 实例
- onBeforeUnmount(() => {
- if (instance) {
- window.removeEventListener("resize", onResize);
- // 移除所有的监听器
- // Handsontable.hooks.destroy && Handsontable.hooks.destroy(instance);
- instance.destroy();
- instance = null;
- }
- if (clickPositionHandler)
- off(document.body, "mouseup", clickPositionHandler);
- });
- // 边框样式
- const borderStyle = computed(() => {
- if (props.border === "none") {
- return {
- border: "none",
- };
- }
- const borders = props.border.split(",");
- const style = {} as any;
- borders.forEach((border) => {
- style[`border-${border}`] = "1px solid #d4d4d4";
- });
- return style;
- });
- return {
- handsontableRef,
- borderStyle,
- Render,
- Update,
- Load,
- GetInstance,
- };
- },
- });
- </script>
- <style lang="scss" scoped>
- .handsontable-container {
- position: relative;
- width: 100%;
- height: 100%;
- @include scrollbar-handsontable;
- }
- .table-wrapper {
- width: 100%;
- height: 100%;
- overflow: hidden;
- /* > :deep(.ht_master) {
- } */
- > :deep(.ht_clone_top_left_corner),
- > :deep(.ht_clone_top) {
- .htCore {
- .real-th {
- position: relative;
- overflow: visible;
- &.ht__highlight {
- .relative {
- background-color: #dad8d6 !important;
- }
- }
- .relative {
- position: absolute;
- bottom: 0;
- left: 0;
- padding: 0;
- width: 100%;
- background-color: $smoke;
- &:hover {
- background-color: #dfdfdf !important;
- }
- }
- }
- }
- }
- /* > :deep(.ht_clone_bottom) {
- } */
- > :deep(.ht_clone_left) {
- // overflow: visible !important;
- .wtHolder {
- width: 100% !important;
- }
- .htCore {
- transition: box-shadow 0.3s ease;
- &.shadow {
- box-shadow: 4px 0 4px -3px rgba(0, 0, 0, 0.2);
- }
- /* tbody {
- // 这段代码会导致小屏幕上缩放分辨率时,表格行错位的问题
- th {
- display: flex;
- align-items: center;
- justify-content: center;
- }
- } */
- }
- }
- > :deep(.ht_clone_top_left_corner) {
- .htCore {
- thead {
- tr {
- th {
- border-color: $gainsboro;
- }
- }
- }
- }
- }
- /* :deep( > .handsontableInputHolder){
- }
- */
- > :deep(.ht_master),
- > :deep(.ht_clone_top),
- > :deep(.ht_clone_bottom),
- > :deep(.ht_clone_left),
- > :deep(.ht_clone_top_left_corner),
- > :deep(.handsontableInputHolder) {
- .htCore {
- td,
- th {
- // 当前选中行的样式
- &.current-row {
- background-color: $light-sky-blue !important;
- }
- &.show-highlight {
- background-color: #c1e1fc !important;
- }
- // 当前选中单元格的样式
- &.current {
- &.highlight {
- background-color: #c1e1fc !important;
- .custom-button {
- display: inline !important;
- vertical-align: middle;
- margin: -1px 0 0 2px;
- }
- }
- }
- }
- th {
- border-color: $gainsboro !important;
- background-color: $smoke !important;
- color: $black;
- cursor: pointer;
- &:hover {
- background-color: #dfdfdf !important;
- }
- &.ht__highlight {
- background-color: #dad8d6 !important;
- .colHeader,
- .rowHeader {
- font-weight: bold;
- color: #2577c2;
- }
- }
- }
- tbody {
- tr {
- &.hover {
- td {
- background-color: $ghost-grey;
- }
- }
- td {
- transition: background-color 0.15s;
- border-color: $gainsboro;
- color: $black;
- &.bg-danger {
- background-color: #fbe3e3;
- }
- &.font-danger {
- color: $danger;
- }
- &.wcSingleLine {
- white-space: nowrap;
- }
- &.htMiddle {
- vertical-align: middle;
- }
- }
- }
- }
- }
- .htBorders {
- .wtBorder.corner {
- cursor: cell;
- }
- }
- }
- // 没有左边框
- > :deep(.ht_clone_left) {
- .htCore {
- th {
- border-left: none;
- }
- }
- }
- > :deep(.ht_clone_top_left_corner) {
- .htCore {
- thead {
- th:first-child {
- border-left: none;
- }
- }
- }
- }
- // 没有上边框
- > :deep(.ht_master),
- > :deep(.ht_clone_top),
- > :deep(.ht_clone_bottom),
- > :deep(.ht_clone_left),
- > :deep(.ht_clone_top_left_corner),
- > :deep(.handsontableInputHolder) {
- .htCore {
- thead {
- tr {
- th {
- border-top: none !important;
- }
- }
- }
- /*tbody {
- tr {
- td {
- border-left: none !important;
- }
- }
- }*/
- }
- }
- :deep(.red) {
- color: red !important;
- }
- :deep(.green) {
- color: green !important;
- }
- :deep(.stateCheckBox::after) {
- content: "";
- display: block;
- width: 7px;
- height: 7px;
- background-color: #007bff;
- transform: translate(3px, 3px);
- }
- // 调整列大小的手柄
- :deep(.manualColumnResizer) {
- cursor: ew-resize;
- transform: translateX(3px);
- }
- // 调整行大小的手柄
- :deep(.manualRowResizer) {
- cursor: ns-resize;
- transform: translateY(3px);
- }
- }
- </style>
|