<script lang="ts" setup>
import { reactive, ref, computed, unref, watchEffect } from 'vue';

const props = defineProps({
  /**列表高度 */
  height: {
    type: Number,
    default: 300,
  },
  /**列表项高度 */
  itemHeight: {
    type: Number,
    default: 30,
  },
  /**数据 */
  data: {
    type: Array,
    default: () => [],
  },
  /**预先兜底缓存数量 */
  cache: {
    type: Number,
    default: 2,
  },

  /**列 */
  columns: {
    type: Array,
    default: () => [],
  },
  selectedFrame: {
    type: Number,
    default: 0,
  },
  onSelectedFrame: {
    type: Function,
    default: () => {},
  },
  onScrollBottom: {
    type: Function,
    default: () => {},
  },
});

const state = reactive<any>({
  start: 0,
  end: 10,
  scrollOffset: 0,
  cacheData: [],
});
const virtualListRef = ref();

const getWrapperStyle = computed(() => {
  const { height } = props;
  return {
    height: `${height}px`,
  };
});

const getInnerStyle = computed(() => {
  return {
    height: `${unref(getTotalHeight)}px`,
    width: '100%',
  };
});

const getListStyle = computed(() => {
  return {
    willChange: 'transform',
    transform: `translateY(${state.scrollOffset}px)`,
  };
});

// 数据数量
const total = computed(() => {
  return props.data.length;
});

// 总体高度
const getTotalHeight = computed(() => {
  return unref(total) * props.itemHeight;
});

// 当前屏幕显示的数量
const clientCount = computed(() => {
  return Math.ceil(props.height / props.itemHeight);
});

// 当前屏幕显示的数据
const clientData = computed<any[]>(() => {
  return props.data.slice(state.start, state.end);
});

const onScroll = (e: any) => {
  const { scrollHeight, scrollTop, clientHeight } = e.target;
  if (state.scrollOffset === scrollTop) return;
  const { cache, height, itemHeight } = props;
  const cacheCount = Math.max(1, cache);

  let startIndex = Math.floor(scrollTop / itemHeight);

  const endIndex = Math.max(
    0,
    Math.min(unref(total), startIndex + unref(clientCount) + cacheCount)
  );

  if (startIndex > cacheCount) {
    startIndex = startIndex - cacheCount;
  }

  // 偏移量
  const offset = scrollTop - (scrollTop % itemHeight);
  Object.assign(state, {
    start: startIndex,
    end: endIndex,
    scrollOffset: offset,
  });

  // 底部小于高度时触发
  if (scrollHeight - scrollTop - clientHeight < height) {
    props.onScrollBottom(endIndex);
  }
};

watchEffect(() => {
  clientData.value.forEach((_, index) => {
    const currentIndex = state.start + index;
    if (Object.hasOwn(state.cacheData, currentIndex)) return;
    state.cacheData[currentIndex] = {
      top: currentIndex * props.itemHeight,
      height: props.itemHeight,
      bottom: (currentIndex + 1) * props.itemHeight,
      index: currentIndex,
    };
  });
});
</script>

<template>
  <div class="table">
    <div class="thead">
      <div class="thead-item" v-for="v in columns">
        {{ v }}
      </div>
    </div>
    <div
      class="virtual-list-wrapper"
      ref="wrapperRef"
      :style="getWrapperStyle"
      @scroll="onScroll"
    >
      <div class="virtual-list-inner" ref="innerRef" :style="getInnerStyle">
        <div class="virtual-list" :style="getListStyle" ref="virtualListRef">
          <div
            class="tbody"
            v-for="(item, index) in clientData"
            :key="index + state.start"
            :style="{
              height: itemHeight + 'px',
              backgroundColor:
                item.number === props.selectedFrame
                  ? 'blue'
                  : item.bg
                  ? `#${Number(item.bg).toString(16)}`
                  : '',
              color:
                item.number === props.selectedFrame
                  ? 'white'
                  : item.fg
                  ? `#${Number(item.fg).toString(16)}`
                  : '',
            }"
            @click="onSelectedFrame(item.number)"
          >
            <div class="tbody-item" v-for="col in item.columns">
              {{ col }}
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<style lang="css" scoped>
.virtual-list-wrapper {
  position: relative;
  overflow-y: auto;
}

.table {
  display: flex;
  flex-direction: column;
  height: 100%;
}
.thead {
  display: flex;
  flex-direction: row;
}
.thead-item {
  white-space: nowrap;
  padding-bottom: 0.25rem;
  padding-top: 0.25rem;
  padding-left: 0.5rem;
  padding-right: 0.5rem;
  text-align: left;
  font-size: 0.875rem;
  line-height: 1.5rem;
  font-weight: 600;
  /* flex-basis: 100%; */
}
.tbody {
  display: flex;
  flex-direction: row;
  align-items: center;
  border-top: 1px #f0f0f0 solid;
  cursor: pointer;
}
.tbody-item {
  padding-left: 0.5rem;
  padding-right: 0.5rem;
  font-size: 0.875rem;
  line-height: 1.5rem;
  /* flex-basis: 100%; */
  text-align: left;
}

.thead-item:nth-child(1),
.tbody-item:nth-child(1) {
  flex-basis: 5rem;
  width: 5rem;
}
.tbody-item:nth-child(1) {
  text-align: right;
}
.thead-item:nth-child(2),
.tbody-item:nth-child(2),
.thead-item:nth-child(3),
.tbody-item:nth-child(3),
.thead-item:nth-child(4),
.tbody-item:nth-child(4) {
  flex-basis: 8rem;
  width: 8rem;
  overflow-y: auto;
}
.thead-item:nth-child(5),
.tbody-item:nth-child(5) {
  flex-basis: 7rem;
  width: 7rem;
}
.thead-item:nth-child(6),
.tbody-item:nth-child(6) {
  flex-basis: 6rem;
  width: 6rem;
}
.tbody-item:nth-child(6) {
  text-align: right;
}
.thead-item:nth-child(7),
.tbody-item:nth-child(7) {
  text-align: left;
  text-wrap: nowrap;
  flex: 1;
  width: 5rem;
  overflow-y: auto;
}

/* 修改滚动条的样式 */
.thead-item:nth-child(2)::-webkit-scrollbar,
.tbody-item:nth-child(2)::-webkit-scrollbar,
.tbody-item:nth-child(3)::-webkit-scrollbar,
.tbody-item:nth-child(4)::-webkit-scrollbar,
.tbody-item:nth-child(7)::-webkit-scrollbar {
  width: 4px; /* 设置滚动条宽度 */
  height: 4px;
}
.thead-item:nth-child(2)::-webkit-scrollbar-track,
.tbody-item:nth-child(2)::-webkit-scrollbar-track,
.tbody-item:nth-child(3)::-webkit-scrollbar-track,
.tbody-item:nth-child(4)::-webkit-scrollbar-track,
.tbody-item:nth-child(7)::-webkit-scrollbar-track {
  background-color: #f0f0f0; /* 设置滚动条轨道背景颜色 */
}
.thead-item:nth-child(2)::-webkit-scrollbar-thumb,
.tbody-item:nth-child(2)::-webkit-scrollbar-thumb,
.tbody-item:nth-child(3)::-webkit-scrollbar-thumb,
.tbody-item:nth-child(4)::-webkit-scrollbar-thumb,
.tbody-item:nth-child(7)::-webkit-scrollbar-thumb {
  background-color: #bfbfbf; /* 设置滚动条滑块颜色 */
}
.thead-item:nth-child(2)::-webkit-scrollbar-thumb:hover,
.tbody-item:nth-child(2)::-webkit-scrollbar-thumb:hover,
.tbody-item:nth-child(3)::-webkit-scrollbar-thumb:hover,
.tbody-item:nth-child(4)::-webkit-scrollbar-thumb:hover,
.tbody-item:nth-child(7)::-webkit-scrollbar-thumb:hover {
  background-color: #1890ff; /* 设置鼠标悬停时滚动条滑块颜色 */
}
</style>
