From 625e8628ec543cda47d49c69055c5a22b17f1a7d Mon Sep 17 00:00:00 2001 From: jeremyjone Date: Mon, 17 Apr 2023 20:44:45 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E2=9C=A8=E6=B7=BB=E5=8A=A0=E5=B1=95?= =?UTF-8?q?=E7=A4=BA=E6=95=B0=E6=8D=AE=E7=9A=84=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- demo/demo.vue | 2 +- src/components/column/index.vue | 45 ++++++++++--- src/components/common/GanttBody.vue | 28 +++++--- src/components/common/TableBody.vue | 60 +++++++++++++---- .../container/SyncScrollContainer.vue | 5 ++ src/components/root/index.vue | 19 ++++-- src/composables/useInView.ts | 64 +++++++++++++++++++ src/composables/useStyle.ts | 9 ++- src/models/data/all.ts | 2 + src/models/data/row.ts | 16 +++-- src/models/param/index.ts | 3 +- src/models/param/param.ts | 19 ++++++ src/models/param/styles.ts | 15 +++++ src/store/index.ts | 12 +++- 14 files changed, 248 insertions(+), 51 deletions(-) create mode 100644 src/composables/useInView.ts create mode 100644 src/models/param/param.ts diff --git a/demo/demo.vue b/demo/demo.vue index 8595c45..a574663 100644 --- a/demo/demo.vue +++ b/demo/demo.vue @@ -33,7 +33,7 @@ let id = 0; const ganttData = reactive([]); -for (let i = 0; i < 50; i++) { +for (let i = 0; i < 500; i++) { onAdd(); } diff --git a/src/components/column/index.vue b/src/components/column/index.vue index 38d18f9..47abfce 100644 --- a/src/components/column/index.vue +++ b/src/components/column/index.vue @@ -1,5 +1,5 @@ diff --git a/src/components/common/GanttBody.vue b/src/components/common/GanttBody.vue index e0e0b52..f7c3e6e 100644 --- a/src/components/common/GanttBody.vue +++ b/src/components/common/GanttBody.vue @@ -3,36 +3,44 @@ class="xg-gantt-body" :style="{ height: bodyHeight, width: `${ganttWidth}px` }" > -
+
diff --git a/src/components/common/TableBody.vue b/src/components/common/TableBody.vue index c5ddd9d..0fcf54e 100644 --- a/src/components/common/TableBody.vue +++ b/src/components/common/TableBody.vue @@ -1,5 +1,5 @@ diff --git a/src/components/container/SyncScrollContainer.vue b/src/components/container/SyncScrollContainer.vue index e9ce4fc..6e62bf4 100644 --- a/src/components/container/SyncScrollContainer.vue +++ b/src/components/container/SyncScrollContainer.vue @@ -12,6 +12,7 @@ // This component idea from https://github.com/metawin-m/vue-scroll-sync import useBus from '@/composables/useBus'; import { uuid } from '@/utils/common'; +import useInView from '@/composables/useInView'; import { onMounted, reactive, ref } from 'vue'; const props = defineProps({ @@ -59,6 +60,8 @@ function scrollFunc(e: any) { scrollAction.y = e.target.scrollTop; } +const { setTop } = useInView(); + function handleScroll(e: any) { // 禁用条件 if (props.disableHorizontal && ['left', 'right'].includes(direction.value)) { @@ -127,6 +130,8 @@ onMounted(() => { container!.scrollTop = props.proportional ? (paneHeight * data.scrollTop) / scrollTopOffset : data.scrollTop; + + setTop(container!.scrollTop); } if ( !data.disableHorizontal && diff --git a/src/components/root/index.vue b/src/components/root/index.vue index 197119f..f538f5b 100644 --- a/src/components/root/index.vue +++ b/src/components/root/index.vue @@ -17,13 +17,7 @@ - - -
+
@@ -59,6 +53,7 @@ import { getCurrentInstance, onMounted, onUpdated, ref, toRefs } from 'vue'; import rootProps from './rootProps'; import useData from '@/composables/useData'; import useStyle from '@/composables/useStyle'; +import useInView from '@/composables/useInView'; import { useResizeObserver } from '@vueuse/core'; const containerId = uuid(10); @@ -83,6 +78,15 @@ onMounted(getScrollGapSize); onUpdated(getScrollGapSize); // #endregion +// #region 得到组件高度,并保存 +const { setRootHeight } = useInView(); +onMounted(() => { + setRootHeight( + Math.max(ganttRef.value.$el.offsetHeight, ganttRef.value.$el.clientHeight) + ); +}); +// #endregion + // #region 处理样式参数 const { setStyles, $styleBox } = useStyle(); setStyles(props); @@ -124,6 +128,7 @@ console.log('.....root', getCurrentInstance()); .table-container { height: 100%; display: inline-block; + position: relative; } .mid-separate-line { diff --git a/src/composables/useInView.ts b/src/composables/useInView.ts new file mode 100644 index 0000000..b972223 --- /dev/null +++ b/src/composables/useInView.ts @@ -0,0 +1,64 @@ +import useStore from '@/store'; +import { computed, reactive, watch } from 'vue'; +import useStyle from './useStyle'; +import type RowItem from '@/models/data/row'; + +export default () => { + const store = useStore(); + + const currentTop = computed(() => store.$param.currentTop); + function setTop(t: number) { + store.$param.currentTop = t; + } + + const rootHeight = computed(() => store.$param.rootHeight); + function setRootHeight(h: number) { + store.$param.rootHeight = h; + } + + const { rowHeight } = useStyle(); + + // 预加载条数 + const preload = 5; + + // 数据展示最上面的 index + const top = computed(() => { + const index = Math.ceil(currentTop.value / rowHeight.value); + return Math.max(index - preload, 0); + }); + + // 数据展示最下面的 index + const bottom = computed(() => { + const count = Math.ceil(rootHeight.value / rowHeight.value); + const t = Math.ceil(currentTop.value / rowHeight.value) + count + preload; + return Math.min(t, store.$data.length); + }); + + // 切出要展示的数据 + const inView = reactive([]); + watch( + () => [top.value, bottom.value], + () => { + for (let i = inView.length - 1; i >= 0; i--) { + if ( + inView[i].flatIndex < top.value || + inView[i].flatIndex > bottom.value + ) { + inView.splice(i, 1); + } + } + + for (let i = top.value; i < bottom.value; i++) { + if (!~inView.findIndex(v => v.flatIndex === i)) { + inView.push(store.$data.flatData[i]); + } + } + } + ); + + return { + inView, + setRootHeight, + setTop + }; +}; diff --git a/src/composables/useStyle.ts b/src/composables/useStyle.ts index 3e88ed7..7cb53b4 100644 --- a/src/composables/useStyle.ts +++ b/src/composables/useStyle.ts @@ -1,11 +1,13 @@ import type rootProps from '@/components/root/rootProps'; import { useStore } from '@/store'; -import { type ExtractPropTypes, ref } from 'vue'; +import { type ExtractPropTypes, ref, computed } from 'vue'; export default () => { const store = useStore(); - const bodyHeight = ref(`${20 * store.$data.length}px`); + const rowHeight = computed(() => store.$styleBox.rowHeight); + + const bodyHeight = ref(`${rowHeight.value * store.$data.length}px`); const setStyles = (props: ExtractPropTypes) => { store.$styleBox.setBorder(props.border); @@ -13,7 +15,8 @@ export default () => { store.$styleBox.ganttColumnSize = props.ganttColumnSize; store.$styleBox.unit = props.unit; + store.$styleBox.rowHeight = props.rowHeight; }; - return { bodyHeight, setStyles, $styleBox: store.$styleBox }; + return { rowHeight, bodyHeight, setStyles, $styleBox: store.$styleBox }; }; diff --git a/src/models/data/all.ts b/src/models/data/all.ts index 505e54a..3906734 100644 --- a/src/models/data/all.ts +++ b/src/models/data/all.ts @@ -218,9 +218,11 @@ export default class AllData { private __flatten() { this.flatData = []; + let index = 0; const fn = (data: RowItem[]) => { for (let i = 0; i < data.length; i++) { + data[i].flatIndex = index++; this.flatData.push(data[i]); if (data[i].isExpand && isArray(data[i].children)) { diff --git a/src/models/data/row.ts b/src/models/data/row.ts index 81194c7..106c6c9 100644 --- a/src/models/data/row.ts +++ b/src/models/data/row.ts @@ -2,12 +2,11 @@ * @Author: JeremyJone * @Date: 2021-09-09 15:50:52 * @LastEditors: JeremyJone - * @LastEditTime: 2023-04-15 23:23:12 + * @LastEditTime: 2023-04-17 15:04:42 * @Description: 一条数据类 */ import { Variables } from '@/constants/vars'; -import { type HeaderDateUnit } from '@/typings/ParamOptions'; import { uuid } from '@/utils/common'; import { cloneDeep, isEqual } from 'lodash'; import { XDate } from '../param/date'; @@ -23,6 +22,11 @@ export default class RowItem { */ index: number = 0; + /** + * 该数据在所有可展示的列表中的索引位置(渲染用) + */ + flatIndex: number = 0; + /** * 当前数据的父级路径集合 */ @@ -171,11 +175,11 @@ export default class RowItem { // 首先判断起始日期不能大于结束日期 if ( - date.compareTo(this.end.getOffset(Variables.time.millisecondOfDay)) === + date.compareTo(this.end.getOffset(Variables.time.millisecondOf.day)) === 'r' ) this.data[this.options.endLabel] = date.getOffset( - Variables.time.millisecondOfDay + Variables.time.millisecondOf.day ).date; // if (!linkage) return; @@ -208,11 +212,11 @@ export default class RowItem { // 首先判断起始日期不能大于结束日期 if ( - date.compareTo(this.start.getOffset(Variables.time.millisecondOfDay)) === + date.compareTo(this.start.getOffset(Variables.time.millisecondOf.day)) === 'l' ) this.data[this.options.startLabel] = date.getOffset( - Variables.time.millisecondOfDay + Variables.time.millisecondOf.day ).date; // if (!linkage) return; diff --git a/src/models/param/index.ts b/src/models/param/index.ts index 27431fd..31a27a3 100644 --- a/src/models/param/index.ts +++ b/src/models/param/index.ts @@ -1,5 +1,6 @@ import SlotsBox from './slotsBox'; import StyleBox from './styles'; import { GanttHeader } from './header'; +import Param from './param'; -export { SlotsBox, StyleBox, GanttHeader }; +export { SlotsBox, StyleBox, GanttHeader, Param }; diff --git a/src/models/param/param.ts b/src/models/param/param.ts new file mode 100644 index 0000000..0378cef --- /dev/null +++ b/src/models/param/param.ts @@ -0,0 +1,19 @@ +export default class Param { + private _currentTop: number = 0; + public get currentTop(): number { + return this._currentTop; + } + + public set currentTop(v: number) { + this._currentTop = v; + } + + private _rootHeight: number = 0; + public get rootHeight(): number { + return this._rootHeight; + } + + public set rootHeight(v: number) { + this._rootHeight = v; + } +} diff --git a/src/models/param/styles.ts b/src/models/param/styles.ts index d0af9b7..08858ad 100644 --- a/src/models/param/styles.ts +++ b/src/models/param/styles.ts @@ -1,3 +1,5 @@ +import Variables from '@/constants/vars'; + type Style = Record; export default class StyleBox { @@ -36,4 +38,17 @@ export default class StyleBox { public set unit(v: HeaderDateUnit) { this.__unit = v; } + + private _rowHeight: number = Variables.default.rowHeight; + public get rowHeight(): number { + return this._rowHeight; + } + + public set rowHeight(v: number | string) { + if (typeof v === 'string') { + this._rowHeight = parseInt(v); + } else { + this._rowHeight = v; + } + } } diff --git a/src/store/index.ts b/src/store/index.ts index 5b2ab51..a305327 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -9,7 +9,7 @@ import { inject, provide, reactive, type Ref, ref } from 'vue'; import EventBus from '@/utils/bus'; import { AllData } from '@/models/data'; -import { GanttHeader, SlotsBox, StyleBox } from '@/models/param'; +import { GanttHeader, Param, SlotsBox, StyleBox } from '@/models/param'; export const initStore = () => { const Bus = reactive(new EventBus()); @@ -29,6 +29,9 @@ export const initStore = () => { const ganttHeader = reactive(new GanttHeader()); provide('ganttHeader', ganttHeader); + + const param = reactive(new Param()); + provide('$param', param); }; export const useStore = () => { @@ -61,7 +64,12 @@ export const useStore = () => { /** * 甘特图的表头类 */ - ganttHeader: inject('ganttHeader') as GanttHeader + ganttHeader: inject('ganttHeader') as GanttHeader, + + /** + * 获取各种参数 + */ + $param: inject('$param') as Param }; };