Commit 23075f92881d5aa9695db1b445386fe452b81232

Authored by ww
1 parent 4b030056

perf: implement data board component add && copy layout recommend algorithm

... ... @@ -110,7 +110,7 @@ export interface DataComponentRecord {
110 110
111 111 export interface AddDataComponentParams {
112 112 boardId: string;
113   - record: Partial<DataComponentRecord>;
  113 + record: Partial<DataComponentRecord> & { layout?: Partial<Layout> };
114 114 }
115 115
116 116 export interface Layout {
... ...
... ... @@ -103,7 +103,7 @@
103 103 unref(chartRef)?.setOption(props?.layout?.chartOption || {});
104 104 }, 500);
105 105
106   - watch(() => props.layout.chartOption, updateChartType);
  106 + watch(() => props.layout.componentType, updateChartType);
107 107
108 108 let timeout: Nullable<number> = null;
109 109
... ...
... ... @@ -6,6 +6,7 @@ export enum MoreActionEvent {
6 6
7 7 // export enum
8 8
  9 +export const DEFAULT_MAX_COL = 24;
9 10 export const DEFAULT_WIDGET_WIDTH = 6;
10 11 export const DEFAULT_WIDGET_HEIGHT = 6;
11 12
... ...
... ... @@ -11,11 +11,16 @@
11 11 import { useMessage } from '/@/hooks/web/useMessage';
12 12 import { decode } from '../../config/config';
13 13 import { ComponentInfo } from '/@/api/dataBoard/model';
  14 + import { useCalcGridLayout } from '../../hook/useCalcGridLayout';
14 15
15 16 interface DataComponentRouteParams extends RouteParams {
16 17 id: string;
17 18 }
18 19
  20 + const props = defineProps<{
  21 + layout: DataBoardLayoutInfo[];
  22 + }>();
  23 +
19 24 const emit = defineEmits(['update', 'create', 'register']);
20 25
21 26 const ROUTE = useRoute();
... ... @@ -62,21 +67,25 @@
62 67 resetForm();
63 68 };
64 69
  70 + const { calcLayoutInfo } = useCalcGridLayout();
65 71 const handleAddComponent = async (value: Recordable) => {
66 72 try {
67 73 if (!unref(frontId)) {
68 74 createMessage.warning('请选择可视化组件');
69 75 return;
70 76 }
  77 + const layout = calcLayoutInfo(unref(props.layout));
  78 + console.log({ ...layout });
71 79 changeOkLoading(true);
72 80 await addDataComponent({
73 81 boardId: unref(boardId),
74   - record: { dataBoardId: unref(boardId), frontId: unref(frontId), ...value },
  82 + record: { dataBoardId: unref(boardId), frontId: unref(frontId), ...value, layout },
75 83 });
76 84 createMessage.success('创建成功');
77 85 closeModal();
78 86 emit('create');
79 87 } catch (error) {
  88 + console.log(error);
80 89 // createMessage.error('创建失败');
81 90 } finally {
82 91 changeOkLoading(false);
... ...
... ... @@ -9,6 +9,7 @@
9 9 import { useModal } from '/@/components/Modal';
10 10 import {
11 11 decode,
  12 + DEFAULT_MAX_COL,
12 13 DEFAULT_WIDGET_HEIGHT,
13 14 DEFAULT_WIDGET_WIDTH,
14 15 isBataBoardSharePage,
... ... @@ -40,6 +41,7 @@
40 41 import HistoryTrendModal from './components/HistoryTrendModal.vue';
41 42 import trendIcon from '/@/assets/svg/trend.svg';
42 43 import backIcon from '/@/assets/images/back.png';
  44 + import { useCalcGridLayout } from '../hook/useCalcGridLayout';
43 45
44 46 const ROUTE = useRoute();
45 47
... ... @@ -71,7 +73,7 @@
71 73 const draggable = ref(!unref(getIsSharePage));
72 74 const resizable = ref(!unref(getIsSharePage));
73 75
74   - const GirdLayoutColNum = 24;
  76 + const GirdLayoutColNum = DEFAULT_MAX_COL;
75 77 const GridLayoutMargin = 20;
76 78
77 79 const handleBack = () => {
... ... @@ -310,6 +312,8 @@
310 312 openModal(true, { isEdit: true, record });
311 313 };
312 314
  315 + const { calcLayoutInfo } = useCalcGridLayout();
  316 +
313 317 const handleCopy = async (id: string) => {
314 318 const record = unref(dataBoardList).find((item) => item.i === id);
315 319 try {
... ... @@ -325,16 +329,13 @@
325 329 const _id = data.data.id;
326 330 const layoutInfo = getLayoutInfo();
327 331
  332 + const newGridLayout = calcLayoutInfo(unref(dataBoardList), {
  333 + width: record?.w || DEFAULT_WIDGET_HEIGHT,
  334 + height: record?.h || DEFAULT_WIDGET_HEIGHT,
  335 + });
328 336 layoutInfo.push({
329 337 id: _id,
330   - // x: (unref(layoutInfo).length * 2) % (GirdLayoutColNum || 24),
331   - // y: unref(layoutInfo).length + (GirdLayoutColNum || 24),
332   - // w: record?.w || DEFAULT_WIDGET_WIDTH,
333   - // h: record?.h || DEFAULT_WIDGET_HEIGHT,
334   - h: record?.h,
335   - w: record?.w,
336   - x: record?.x,
337   - y: record?.y,
  338 + ...newGridLayout,
338 339 } as Layout);
339 340
340 341 await updateDataBoardLayout({
... ... @@ -471,6 +472,7 @@
471 472 </Spin>
472 473 </section>
473 474 <DataBindModal
  475 + :layout="dataBoardList"
474 476 @register="register"
475 477 @update="handleUpdateComponent"
476 478 @create="getDataBoardComponent"
... ...
  1 +import { unref } from 'vue';
  2 +import { Layout } from 'vue3-grid-layout';
  3 +import { DEFAULT_WIDGET_HEIGHT, DEFAULT_WIDGET_WIDTH, DEFAULT_MAX_COL } from '../config/config';
  4 +
  5 +interface GapRecord {
  6 + maxGap: number;
  7 + startIndex: Nullable<number>;
  8 + endIndex: Nullable<number>;
  9 +}
  10 +
  11 +export function useCalcGridLayout() {
  12 + const calcLayoutInfo = (
  13 + layoutInfo: Layout[],
  14 + randomLayout = { width: DEFAULT_WIDGET_WIDTH, height: DEFAULT_WIDGET_HEIGHT }
  15 + ) => {
  16 + let maxWidth = 0;
  17 + let maxHeight = 0;
  18 + let maxWidthRecord = {} as unknown as Layout;
  19 + let maxHeightRecord = {} as unknown as Layout;
  20 +
  21 + for (const item of unref(layoutInfo)) {
  22 + const { x, y, h, w } = item;
  23 + if (x + w > maxWidth) {
  24 + maxWidth = x + w;
  25 + maxWidthRecord = item;
  26 + }
  27 + if (y + h > maxHeight) {
  28 + maxHeight = y + h;
  29 + maxHeightRecord = item;
  30 + }
  31 + }
  32 +
  33 + maxWidth = maxWidthRecord.x + maxWidthRecord.w;
  34 + maxHeight = maxHeightRecord.y + maxHeightRecord.h;
  35 +
  36 + const array = Array.from({ length: maxHeight }, (_value) => {
  37 + return Array.from({ length: maxWidth });
  38 + });
  39 +
  40 + for (const item of layoutInfo) {
  41 + const { x, y, w, h } = item;
  42 +
  43 + for (let i = 0; i < h; i++) {
  44 + const rowIndex = y + i > array.length - 1 ? array.length - 1 : y + i;
  45 + const colEnd = x + w;
  46 + const row = array[rowIndex];
  47 + row.fill(true, x, colEnd);
  48 + }
  49 + }
  50 +
  51 + const checkAreaIsAvaliable = (rowIndex: number, rowRecord: GapRecord[]) => {
  52 + const { height } = randomLayout;
  53 +
  54 + for (const { startIndex: colStartIndex } of rowRecord) {
  55 + let record: GapRecord = { maxGap: 0, startIndex: null, endIndex: null };
  56 + const heightGapRecord: GapRecord[] = [];
  57 + for (let i = 0; i < height; i++) {
  58 + const rowStartIndex = rowIndex + i > array.length - 1 ? array.length - 1 : rowIndex + i;
  59 + const row = array[rowStartIndex];
  60 + const col = row[colStartIndex!];
  61 + if (col) {
  62 + if (record.maxGap > 0) heightGapRecord.push(record);
  63 + record = { maxGap: 0, startIndex: null, endIndex: null };
  64 + }
  65 + if (!col) {
  66 + record = {
  67 + maxGap: record.maxGap + 1,
  68 + startIndex: record.startIndex === null ? rowStartIndex : record.startIndex,
  69 + endIndex: rowStartIndex,
  70 + };
  71 + }
  72 + if (i + 1 === height) if (record.maxGap > 0) heightGapRecord.push(record);
  73 + }
  74 + const minHeight = heightGapRecord.length
  75 + ? Math.min(...heightGapRecord.map((item) => item.maxGap))
  76 + : 0;
  77 + if (minHeight >= height) {
  78 + let flag = true;
  79 + for (let colIndex = colStartIndex!; colIndex < record.endIndex!; colIndex++) {
  80 + for (let _rowIndex = rowIndex; _rowIndex < height; _rowIndex++) {
  81 + if (array[_rowIndex][colIndex]) {
  82 + flag = false;
  83 + break;
  84 + }
  85 + }
  86 + }
  87 + if (flag) return { y: rowIndex, x: colStartIndex!, flag: true };
  88 + }
  89 + }
  90 +
  91 + return { flag: false, x: 0, y: 0 };
  92 + };
  93 +
  94 + for (let rowIndex = 0; rowIndex < array.length; rowIndex++) {
  95 + const row = array[rowIndex];
  96 + let record: GapRecord = { maxGap: 0, startIndex: null, endIndex: null };
  97 + const widthGapRecord: GapRecord[] = [];
  98 +
  99 + const { width } = unref(randomLayout);
  100 +
  101 + for (let colIndex = 0; colIndex < DEFAULT_MAX_COL; colIndex++) {
  102 + const col = row[colIndex];
  103 + if (col) {
  104 + if (record.maxGap > 0) widthGapRecord.push(record);
  105 + record = { maxGap: 0, startIndex: null, endIndex: null };
  106 + }
  107 + if (!col) {
  108 + record = {
  109 + maxGap: record.maxGap + 1,
  110 + startIndex: record.startIndex === null ? colIndex : record.startIndex,
  111 + endIndex: colIndex,
  112 + };
  113 + }
  114 + if (colIndex + 1 === DEFAULT_MAX_COL) if (record.maxGap > 0) widthGapRecord.push(record);
  115 + }
  116 +
  117 + const maxWidth = widthGapRecord.length
  118 + ? Math.max(...widthGapRecord.map((item) => item.maxGap))
  119 + : 0;
  120 +
  121 + if (maxWidth >= width) {
  122 + const maxRecordList = widthGapRecord.filter((item) => item.maxGap >= maxWidth);
  123 + const { flag, x, y } = checkAreaIsAvaliable(rowIndex, maxRecordList);
  124 + if (flag) return { x, y, w: randomLayout.width, h: randomLayout.height };
  125 + }
  126 + }
  127 +
  128 + return { x: 0, y: array.length, w: randomLayout.width, h: randomLayout.height };
  129 + };
  130 +
  131 + return { calcLayoutInfo };
  132 +}
... ...