|
@@ -1,123 +1,131 @@
|
|
|
<script setup lang="ts">
|
|
|
-import { uuid } from "@/utils/getUuid";
|
|
|
+import { reactive, onMounted, defineExpose, defineOptions } from "vue";
|
|
|
|
|
|
defineOptions({
|
|
|
name: "DashboardEditor",
|
|
|
});
|
|
|
|
|
|
+// 内部生成唯一 ID 的方法
|
|
|
+let idCounter = 0;
|
|
|
+
|
|
|
+function generateId() {
|
|
|
+ return `id-${idCounter++}`;
|
|
|
+}
|
|
|
+
|
|
|
const store = reactive({
|
|
|
isDragging: false,
|
|
|
startCell: {
|
|
|
row: 0,
|
|
|
col: 0,
|
|
|
+ td: {},
|
|
|
},
|
|
|
selectedCells: [],
|
|
|
});
|
|
|
|
|
|
+type TableChildren = {
|
|
|
+ type: "tablerow" | "tablecell";
|
|
|
+ id: string;
|
|
|
+ display: boolean;
|
|
|
+ data: any[];
|
|
|
+ rowspan: number | null;
|
|
|
+ colspan: number | null;
|
|
|
+};
|
|
|
+
|
|
|
+type TableType = {
|
|
|
+ type: "tablerow" | "tablecell";
|
|
|
+ id: string;
|
|
|
+ children: TableChildren[];
|
|
|
+};
|
|
|
+
|
|
|
const tableConfig = reactive({
|
|
|
- table: [],
|
|
|
+ table: [] as TableType[],
|
|
|
});
|
|
|
|
|
|
-function startDrag(row: number, col: number, event: MouseEvent) {
|
|
|
- if (event.buttons == 2) return;
|
|
|
+function startDrag(row: number, col: number, tditem, event: MouseEvent) {
|
|
|
+ if (event.buttons === 2) return;
|
|
|
store.isDragging = true;
|
|
|
- store.startCell = { row, col };
|
|
|
+ store.startCell = { row, col, td: tditem };
|
|
|
store.selectedCells = [{ row, col }];
|
|
|
}
|
|
|
|
|
|
-function onMouseMove(row: number, col: number, tritem, tditem) {
|
|
|
+function getMinCell(endRow: number, endCol: number) {
|
|
|
+ const startRow = store.startCell!.row;
|
|
|
+ const startCol = store.startCell!.col;
|
|
|
+
|
|
|
+ return {
|
|
|
+ startRow: Math.min(startRow, endRow),
|
|
|
+ startCol: Math.min(endCol, startCol),
|
|
|
+ endRow: Math.max(startRow, endRow),
|
|
|
+ endCol: Math.max(endCol, startCol),
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+function onMouseMove(row: number, col: number, td) {
|
|
|
if (!store.isDragging) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
+ console.log("getMinCell", getMinCell(row, col));
|
|
|
const startRow = store.startCell!.row;
|
|
|
const startCol = store.startCell!.col;
|
|
|
|
|
|
- const startCell = tableConfig.table[startRow].children[startCol];
|
|
|
- const endCell = tableConfig.table[row].children[col];
|
|
|
-
|
|
|
- const startRowSpan = startCell.rowspan || 1;
|
|
|
- const startColSpan = startCell.colspan || 1;
|
|
|
- const endRowSpan = endCell.rowspan || 1;
|
|
|
- const endColSpan = endCell.colspan || 1;
|
|
|
-
|
|
|
const minRow = Math.min(startRow, row);
|
|
|
- const maxRow = Math.max(startRow + startRowSpan - 1, row + endRowSpan - 1);
|
|
|
+ const maxRow = Math.max(startRow, row);
|
|
|
const minCol = Math.min(startCol, col);
|
|
|
- const maxCol = Math.max(startCol + startColSpan - 1, col + endColSpan - 1);
|
|
|
+ const maxCol = Math.max(startCol, col);
|
|
|
+ console.log("startRow:", startRow, "startCol", startCol);
|
|
|
|
|
|
store.selectedCells = [];
|
|
|
+
|
|
|
+ const tmp = [];
|
|
|
+
|
|
|
for (let r = minRow; r <= maxRow; r++) {
|
|
|
for (let c = minCol; c <= maxCol; c++) {
|
|
|
- store.selectedCells.push({ row: r, col: c });
|
|
|
+ tmp.push({ row: r, col: c });
|
|
|
}
|
|
|
}
|
|
|
- console.log(store.selectedCells);
|
|
|
+
|
|
|
+ // console.log(tmp);
|
|
|
+ tmp.forEach(({ row, col }) => {
|
|
|
+ // console.log(row, col);
|
|
|
+ });
|
|
|
+
|
|
|
+ store.selectedCells = tmp;
|
|
|
}
|
|
|
|
|
|
function endDrag() {
|
|
|
store.isDragging = false;
|
|
|
}
|
|
|
|
|
|
+function getData(row: number, col: number) {
|
|
|
+ return tableConfig.table[row].children[col];
|
|
|
+}
|
|
|
+
|
|
|
function mergeCells() {
|
|
|
if (store.selectedCells.length < 2) return;
|
|
|
|
|
|
const firstCell = store.selectedCells[0];
|
|
|
const mergedCells = store.selectedCells.slice(1);
|
|
|
|
|
|
- const targetCell = tableConfig.table[firstCell.row].children[firstCell.col];
|
|
|
-
|
|
|
- // Calculate the total row and column span
|
|
|
- let maxRow = firstCell.row;
|
|
|
- let maxCol = firstCell.col;
|
|
|
-
|
|
|
- for (const cell of store.selectedCells) {
|
|
|
- const currentCell = tableConfig.table[cell.row].children[cell.col];
|
|
|
- maxRow = Math.max(maxRow, cell.row + (currentCell.rowspan || 1) - 1);
|
|
|
- maxCol = Math.max(maxCol, cell.col + (currentCell.colspan || 1) - 1);
|
|
|
- }
|
|
|
-
|
|
|
- const rowSpan = maxRow - firstCell.row + 1;
|
|
|
- const colSpan = maxCol - firstCell.col + 1;
|
|
|
-
|
|
|
- // Update the first cell's colspan and rowspan
|
|
|
- targetCell.colspan = colSpan;
|
|
|
- targetCell.rowspan = rowSpan;
|
|
|
-
|
|
|
- // Remove other cells and update table structure. Iterate in reverse to avoid index issues
|
|
|
- for (let i = mergedCells.length - 1; i >= 0; i--) {
|
|
|
- const cellToRemove = mergedCells[i];
|
|
|
- const rowIndex = cellToRemove.row;
|
|
|
- const colIndex = cellToRemove.col;
|
|
|
-
|
|
|
- // Remove cell from its row
|
|
|
- tableConfig.table[rowIndex].children.splice(colIndex, 1);
|
|
|
-
|
|
|
- //If the row becomes empty, remove it
|
|
|
- if (tableConfig.table[rowIndex].children.length === 0) {
|
|
|
- tableConfig.table.splice(rowIndex, 1);
|
|
|
- // adjust the start cell if needed
|
|
|
- if (firstCell.row > rowIndex) {
|
|
|
- firstCell.row--;
|
|
|
- }
|
|
|
-
|
|
|
- // adjust selected cells if needed
|
|
|
- store.selectedCells.forEach(cell => {
|
|
|
- if (cell.row > rowIndex) {
|
|
|
- cell.row--;
|
|
|
- }
|
|
|
- });
|
|
|
+ const minRow = Math.min(...store.selectedCells.map(cell => cell.row));
|
|
|
+ const maxRow = Math.max(...store.selectedCells.map(cell => cell.row));
|
|
|
+ const minCol = Math.min(...store.selectedCells.map(cell => cell.col));
|
|
|
+ const maxCol = Math.max(...store.selectedCells.map(cell => cell.col));
|
|
|
+
|
|
|
+ // 设置合并单元格的显示范围
|
|
|
+ const firstCellData = getData(firstCell.row, firstCell.col);
|
|
|
+ firstCellData.rowspan = maxRow - minRow + 1;
|
|
|
+ firstCellData.colspan = maxCol - minCol + 1;
|
|
|
+
|
|
|
+ // 隐藏其他合并单元格
|
|
|
+ store.selectedCells.forEach(({ row, col }) => {
|
|
|
+ if (row !== firstCell.row || col !== firstCell.col) {
|
|
|
+ const cell = getData(row, col);
|
|
|
+ cell.display = false;
|
|
|
}
|
|
|
+ });
|
|
|
|
|
|
- // Adjust the index of selected cells and other cells after removing
|
|
|
- store.selectedCells.forEach(cell => {
|
|
|
- if (cell.row === rowIndex && cell.col > colIndex) {
|
|
|
- cell.col--;
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- // Clear selection
|
|
|
+ // 清空选区
|
|
|
store.selectedCells = [];
|
|
|
store.isDragging = false;
|
|
|
}
|
|
@@ -130,14 +138,18 @@ function createTable(row: number, column: number) {
|
|
|
for (let j = 0; j < column; j++) {
|
|
|
children.push({
|
|
|
type: "tablecell",
|
|
|
- id: uuid(),
|
|
|
- });
|
|
|
+ id: generateId(),
|
|
|
+ display: true,
|
|
|
+ data: [],
|
|
|
+ rowspan: null,
|
|
|
+ colspan: null,
|
|
|
+ } as TableChildren);
|
|
|
}
|
|
|
|
|
|
tableConfig.table.push({
|
|
|
type: "tablerow",
|
|
|
children: children,
|
|
|
- id: uuid(),
|
|
|
+ id: generateId(),
|
|
|
});
|
|
|
}
|
|
|
}
|
|
@@ -163,27 +175,27 @@ defineExpose({
|
|
|
<table :class="{ isDragging: store.isDragging }">
|
|
|
<tbody>
|
|
|
<tr v-for="(tritem, rowIndex) in tableConfig.table" :key="tritem.id">
|
|
|
- <td
|
|
|
- :id="tditem.id"
|
|
|
- v-for="(tditem, colIndex) in tritem.children"
|
|
|
- :key="tditem.id"
|
|
|
- :colspan="handleSpan(tditem?.colspan)"
|
|
|
- :rowspan="handleSpan(tditem?.rowspan)"
|
|
|
- :class="{
|
|
|
- selected: store.selectedCells.some(
|
|
|
- cell => cell.row === rowIndex && cell.col === colIndex
|
|
|
- ),
|
|
|
- }"
|
|
|
- @mousedown="startDrag(rowIndex, colIndex, $event)"
|
|
|
- @mouseenter.prevent="
|
|
|
- onMouseMove(rowIndex, colIndex, tritem, tditem)
|
|
|
- "
|
|
|
- @contextmenu.stop.prevent="mergeCells"
|
|
|
- >
|
|
|
- <div class="edl-cell-renderer">
|
|
|
- {{ `(${rowIndex} , ${colIndex})` }}
|
|
|
- </div>
|
|
|
- </td>
|
|
|
+ <template v-for="(tditem, colIndex) in tritem.children">
|
|
|
+ <td
|
|
|
+ v-if="tditem.display"
|
|
|
+ :id="tditem.id"
|
|
|
+ :key="tditem.id"
|
|
|
+ :colspan="handleSpan(tditem?.colspan)"
|
|
|
+ :rowspan="handleSpan(tditem?.rowspan)"
|
|
|
+ :class="{
|
|
|
+ selected: store.selectedCells.some(
|
|
|
+ cell => cell.row === rowIndex && cell.col === colIndex
|
|
|
+ ),
|
|
|
+ }"
|
|
|
+ @mousedown="startDrag(rowIndex, colIndex, tditem, $event)"
|
|
|
+ @mouseenter.prevent="onMouseMove(rowIndex, colIndex, tditem)"
|
|
|
+ @contextmenu.stop.prevent="mergeCells"
|
|
|
+ >
|
|
|
+ <div class="edl-cell-renderer">
|
|
|
+ {{ `(${rowIndex} , ${colIndex})` }}
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ </template>
|
|
|
</tr>
|
|
|
</tbody>
|
|
|
</table>
|
|
@@ -211,6 +223,8 @@ table.isDragging {
|
|
|
td {
|
|
|
border: 1px solid white;
|
|
|
text-align: center;
|
|
|
+ color: white;
|
|
|
+ background-color: black;
|
|
|
}
|
|
|
|
|
|
td.selected {
|