Browse Source

表格拖拽优化

main
likunming 6 months ago
parent
commit
13de19a27f
  1. 103
      io.sc.platform.core.frontend/src/platform/components/grid/GridBody.vue
  2. 1
      io.sc.platform.core.frontend/src/platform/components/grid/GridHeader.vue
  3. 114
      io.sc.platform.core.frontend/src/platform/components/grid/TreeGridRow.vue
  4. 73
      io.sc.platform.core.frontend/src/platform/components/grid/WGrid.vue
  5. 2
      io.sc.platform.core.frontend/src/platform/components/grid/ts/grid.ts

103
io.sc.platform.core.frontend/src/platform/components/grid/GridBody.vue

@ -22,20 +22,13 @@
ref="trRef"
:class="props.scope.row[table.selectedField] ? 'selected' : ''"
:props="props.scope"
:draggable="
((typeof props.grid.props.draggable === 'boolean' && props.grid.props.draggable) ||
(typeof props.grid.props.draggable === 'string' && props.grid.props.draggable === 'local')) &&
table.bodyEditStatus === 'none'
? true
: false
"
@click.stop="rowClick($event, scope.row, scope.rowIndex)"
@dblclick.stop="rowDbClick($event, scope.row, scope.rowIndex)"
@dragenter="onDragEnter($event, scope)"
@dragleave="onDragLeave"
@dragover="onDragOver($event, scope)"
@drop="onDrop($event, scope)"
@dragstart="onDragStart($event, scope)"
:draggable="draggableComputed"
@click.stop="draggableComputed ? rowClick($event, scope.row, scope.rowIndex) : () => {}"
@dblclick.stop="draggableComputed ? rowDbClick($event, scope.row, scope.rowIndex) : () => {}"
@dragleave="draggableComputed ? onDragLeave($event) : () => {}"
@dragover="draggableComputed ? onDragOver($event, scope) : () => {}"
@drop="draggableComputed ? onDrop($event, scope) : () => {}"
@dragstart="draggableComputed ? onDragStart($event, scope) : () => {}"
>
<q-td v-if="table.checkboxSelection" class="text-center" style="padding: 0; width: 50px">
<q-checkbox
@ -73,6 +66,7 @@
<script setup lang="ts">
import { computed, inject, ref, toRaw } from 'vue';
import { Tools } from '@/platform';
import { draggableImage } from './ts/grid';
import TreeGridRow from './TreeGridRow.vue';
import GridTd from './GridTd.vue';
import GridEditToolbar from './GridEditToolbar.vue';
@ -142,6 +136,10 @@ const props = defineProps({
return 0;
},
},
allTickedStatus: {
type: Function,
default: () => {},
},
});
const table = inject('table');
@ -177,6 +175,15 @@ const setTreeRowComponentRef = (el, row) => {
}
};
const draggableComputed = computed(() => {
if (typeof props.grid.props.draggable === 'boolean' && props.grid.props.draggable && table.bodyEditStatus === 'none') {
return true;
} else if (typeof props.grid.props.draggable === 'string' && props.grid.props.draggable === 'local' && table.bodyEditStatus === 'none') {
return true;
}
return false;
});
const isSelectedRowComputed = computed(() => {
const selected = props.grid.getSelectedRow();
if (!Tools.isEmpty(selected)) {
@ -196,6 +203,9 @@ const gridTrMiddleHeightComputed = computed(() => {
//
const onDragStart = (e, scope) => {
const img = new Image();
img.src = draggableImage;
e.dataTransfer.setDragImage(img, 0, 0);
const currPageIndex = table.rows.findIndex((item) => {
return item[props.rowKeyName] === scope.row[props.rowKeyName];
});
@ -244,15 +254,6 @@ const removeDragBottomStyle = (e) => {
}
}
};
//
const onDragEnter = (e, scope) => {
if (table.dragRow.rowIndex !== scope.rowIndex && table.dragRow.currPageStartIndex + e.target.parentNode.parentNode.children.length === scope.rowIndex + 1) {
//
addDragBottomStyle(e);
} else if (table.dragRow.rowIndex !== scope.rowIndex) {
addDragTopStyle(e);
}
};
//
const onDragLeave = (e) => {
removeDragTopStyle(e);
@ -260,49 +261,41 @@ const onDragLeave = (e) => {
};
//
const onDragOver = (e, scope) => {
if (
e.offsetY >= gridTrMiddleHeightComputed.value &&
table.dragRow.rowIndex !== scope.rowIndex &&
table.dragRow.currPageStartIndex + e.target.parentNode.parentNode.children.length === scope.rowIndex + 1
) {
removeDragTopStyle(e);
addDragBottomStyle(e);
} else if (
e.offsetY < gridTrMiddleHeightComputed.value &&
table.dragRow.rowIndex !== scope.rowIndex &&
table.dragRow.currPageStartIndex + e.target.parentNode.parentNode.children.length === scope.rowIndex + 1
) {
e.preventDefault();
if (e.target.nodeName === 'TD' && e.offsetY <= gridTrMiddleHeightComputed.value) {
removeDragBottomStyle(e);
addDragTopStyle(e);
} else if (e.target.nodeName === 'TD' && e.offsetY > gridTrMiddleHeightComputed.value) {
removeDragTopStyle(e);
addDragBottomStyle(e);
}
e.preventDefault();
};
//
const onDrop = (e, scope) => {
e.preventDefault();
removeDragTopStyle(e);
removeDragBottomStyle(e);
if (table.dragRow.rowIndex === scope.rowIndex) {
return;
}
const dragRow = table.dragRow.row;
const currPageStartIndex = table.dragRow.currPageStartIndex;
table.rows.splice(table.dragRow.currPageIndex, 1);
if (e.offsetY < gridTrMiddleHeightComputed.value && table.dragRow.rowIndex < scope.rowIndex) {
if (e.offsetY <= gridTrMiddleHeightComputed.value && table.dragRow.rowIndex < scope.rowIndex) {
table.rows.splice(scope.rowIndex - currPageStartIndex - 1, 0, dragRow);
} else if (e.offsetY > gridTrMiddleHeightComputed.value && table.dragRow.rowIndex > scope.rowIndex) {
table.rows.splice(scope.rowIndex - currPageStartIndex + 1, 0, dragRow);
} else {
table.rows.splice(scope.rowIndex - currPageStartIndex, 0, dragRow);
}
removeDragTopStyle(e);
removeDragBottomStyle(e);
const updateData = <any>[];
table.rows.forEach((item, index) => {
if (!Tools.isEmpty(item)) {
item[props.grid.props.orderBy] = currPageStartIndex + index + 1;
item[props.grid.props.draggableOrderBy] = currPageStartIndex + index + 1;
updateData.push(toRaw(item));
}
});
if (typeof props.grid.props.draggable === 'boolean' && props.grid.props.draggable) {
// 访
props.grid.updates(updateData);
@ -311,30 +304,10 @@ const onDrop = (e, scope) => {
props.afterRowDraggable(updateData);
};
//
const allTickedStatus = () => {
// null
// false
// true
const ticked_ = <any>[];
table.rows.forEach((item) => {
if (item[table.tickedField]) {
ticked_.push(item);
}
});
if (ticked_.length === table.rows.length) {
table.allTicked = true;
} else if (ticked_.length > 0) {
table.allTicked = null;
} else {
table.allTicked = false;
}
};
const updateTicked = (evt: Event, row: any) => {
if (table.bodyEditStatus === 'none') {
props.getRow(table.rows, row[props.rowKeyName], false)[table.selectedField] = row[table.tickedField];
allTickedStatus();
props.allTickedStatus();
if (props.grid.props.onUpdateTicked) {
props.grid.emit('updateTicked', evt, row);
}
@ -342,10 +315,6 @@ const updateTicked = (evt: Event, row: any) => {
props.getRow(table.rows, row[props.rowKeyName], false)[table.tickedField] = !props.getRow(table.rows, row[props.rowKeyName], false)[table.tickedField];
}
};
defineExpose({
allTickedStatus,
});
</script>
<style lang="css"></style>

1
io.sc.platform.core.frontend/src/platform/components/grid/GridHeader.vue

@ -188,6 +188,7 @@ const allTickedUpdateFun = (value, evt) => {
} else if (table.bodyEditStatus === 'rowsEdit') {
table.allTicked = false;
}
props.grid.emit('updateTickeds', value);
};
const handlerStickyChildrenColumn = (item, columns) => {

114
io.sc.platform.core.frontend/src/platform/components/grid/TreeGridRow.vue

@ -2,20 +2,13 @@
<q-tr
ref="trRef"
:class="row[table.selectedField] ? 'selected ' : ''"
:draggable="
((typeof props.grid.props.draggable === 'boolean' && props.grid.props.draggable) ||
(typeof props.grid.props.draggable === 'string' && props.grid.props.draggable === 'local')) &&
table.bodyEditStatus === 'none'
? true
: false
"
:draggable="draggableComputed"
@click.stop.prevent="click($event, row, row.rowIndex)"
@dblclick.stop.prevent="dbClick($event, row, row.rowIndex)"
@dragenter="onDragEnter($event, row)"
@dragleave="onDragLeave($event, row)"
@dragover="onDragOver($event, row)"
@drop="onDrop($event, row)"
@dragstart="onDragStart($event, row)"
@dragleave="draggableComputed ? onDragLeave($event, row) : () => {}"
@dragover="draggableComputed ? onDragOver($event, row) : () => {}"
@drop="draggableComputed ? onDrop($event, row) : () => {}"
@dragstart="draggableComputed ? onDragStart($event, row) : () => {}"
>
<q-td class="nowrap text-nowrap">
<div ref="tdDivRef" class="flex flex-nowrap items-center h-full w-full" style="flex-wrap: nowrap">
@ -137,6 +130,7 @@
import { ref, computed, inject, toRaw } from 'vue';
import { Tools, NotifyManager } from '@/platform';
import GridEditToolbar from './GridEditToolbar.vue';
import { draggableImage } from './ts/grid';
const trRef = ref();
const tdDivRef = ref();
@ -235,6 +229,15 @@ const style = {
},
};
const draggableComputed = computed(() => {
if (typeof props.grid.props.draggable === 'boolean' && props.grid.props.draggable && table.bodyEditStatus === 'none') {
return true;
} else if (typeof props.grid.props.draggable === 'string' && props.grid.props.draggable === 'local' && table.bodyEditStatus === 'none') {
return true;
}
return false;
});
const isSelectedRowComputed = computed(() => {
const selected = props.grid.getSelectedRow();
if (!Tools.isEmpty(selected)) {
@ -384,7 +387,7 @@ document.addEventListener('drop', function (e) {
//
const onDragStart = (e, dataRow) => {
const img = new Image();
img.src = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' %3E%3Cpath /%3E%3C/svg%3E`;
img.src = draggableImage;
e.dataTransfer.setDragImage(img, 0, 0);
const selecteds = props.grid.getSelectedRows();
if (
@ -500,30 +503,6 @@ const dragRecordsContains = (dataRow) => {
);
};
//
const isLastNode = (dataRow) => {
if (Tools.isEmpty(dataRow[props.grid.props.foreignKey])) {
if (table.rows[table.rows.length - 1][props.grid.props.primaryKey] === dataRow[props.grid.props.primaryKey]) {
return true;
}
} else {
const parent = props.getRow(table.rows, dataRow[props.grid.props.foreignKey], true);
if (parent.children[parent.children.length - 1][props.grid.props.primaryKey] === dataRow[props.grid.props.primaryKey]) {
return true;
}
}
return false;
};
//
const onDragEnter = (e, dataRow) => {
if (e.target.nodeName === 'TD' && e.offsetY <= gridTrMiddleHeightComputed.value && !dragRecordsContains(dataRow)) {
addDragTopStyle(e);
} else if (isLastNode(dataRow) && e.target.nodeName === 'TD' && e.offsetY > gridTrMiddleHeightComputed.value && !dragRecordsContains(dataRow)) {
addDragBottomStyle(e);
}
};
//
const onDragLeave = (e, dataRow) => {
removeDragTopStyle(e);
@ -549,7 +528,7 @@ const onDragOver = (e, dataRow) => {
if (e.target.nodeName === 'TD' && e.offsetY <= gridTrMiddleHeightComputed.value && !dragRecordsContains(dataRow)) {
removeDragBottomStyle(e);
addDragTopStyle(e);
} else if (isLastNode(dataRow) && e.target.nodeName === 'TD' && e.offsetY > gridTrMiddleHeightComputed.value && !dragRecordsContains(dataRow)) {
} else if (e.target.nodeName === 'TD' && e.offsetY > gridTrMiddleHeightComputed.value && !dragRecordsContains(dataRow)) {
removeDragTopStyle(e);
addDragBottomStyle(e);
}
@ -583,42 +562,57 @@ const removeRecord = (arr, removeData) => {
//
const updateOrderData = <any>[];
//
const nodeSplice = (e, dragIndex, targetIndex, drag, target, arr) => {
if (
(Tools.isEmpty(drag[props.grid.props.foreignKey]) && Tools.isEmpty(target[props.grid.props.foreignKey])) ||
drag[props.grid.props.foreignKey] === target[props.grid.props.foreignKey]
) {
const lessThanResult = dragLessThanTarget(dragIndex, targetIndex);
//
if (e.offsetY <= gridTrMiddleHeightComputed.value && lessThanResult) {
arr.splice(targetIndex - 1, 0, drag);
} else if (e.offsetY > gridTrMiddleHeightComputed.value && !lessThanResult) {
arr.splice(targetIndex + 1, 0, drag);
} else {
arr.splice(targetIndex, 0, drag);
}
} else {
//
if (e.offsetY <= gridTrMiddleHeightComputed.value) {
arr.splice(targetIndex, 0, drag);
} else if (e.offsetY > gridTrMiddleHeightComputed.value) {
arr.splice(targetIndex + 1, 0, drag);
}
}
};
//
const setOrder = (arr) => {
arr.forEach((item, index) => {
item[props.grid.props.orderBy] = index + 1;
item[props.grid.props.draggableOrderBy] = index + 1;
//
updateOrderData.push({ ...toRaw(item), children: null });
});
};
//
const addRecord = (e, arr, addData, targetData, dragIndex, targetIndex) => {
const childrenResetOrder = (e, arr, addData, targetData, dragIndex, targetIndex) => {
for (let i = 0; i < arr.length; i++) {
if (arr[i][props.grid.props.primaryKey] === targetData[props.grid.props.foreignKey]) {
if (
e.offsetY <= gridTrMiddleHeightComputed.value &&
isTrue(dragIndex, targetIndex) &&
addData[props.grid.props.foreignKey] === targetData[props.grid.props.foreignKey]
) {
arr[i].children.splice(targetIndex - 1, 0, addData);
} else if (e.offsetY > gridTrMiddleHeightComputed.value && addData[props.grid.props.foreignKey] !== targetData[props.grid.props.foreignKey]) {
arr[i].children.splice(arr[i].children.length, 0, addData);
} else {
arr[i].children.splice(targetIndex, 0, addData);
}
nodeSplice(e, dragIndex, targetIndex, addData, targetData, arr[i].children);
//
addData[props.grid.props.foreignKey] = arr[i][props.grid.props.primaryKey];
//
setOrder(arr[i].children);
break;
} else if (arr[i].children && arr[i].children.length > 0) {
addRecord(e, arr[i].children, addData, targetData, dragIndex, targetIndex);
childrenResetOrder(e, arr[i].children, addData, targetData, dragIndex, targetIndex);
}
}
};
const isTrue = (dragIndex, targetIndex) => {
const dragLessThanTarget = (dragIndex, targetIndex) => {
if (dragIndex < targetIndex) {
return true;
}
@ -627,23 +621,15 @@ const isTrue = (dragIndex, targetIndex) => {
//
const resetOrderDataHandler = (e, dragRecords, targetData, targetIndex) => {
// 1
dragRecords.forEach((item) => {
//
const itemIndex = removeRecord(table.rows, item);
// 2
if (Tools.isEmpty(targetData[props.grid.props.foreignKey])) {
// 3使splice-11
if (e.offsetY <= gridTrMiddleHeightComputed.value && Tools.isEmpty(item[props.grid.props.foreignKey]) && isTrue(itemIndex, targetIndex)) {
table.rows.splice(targetIndex - 1, 0, item);
} else {
table.rows.splice(targetIndex, 0, item);
}
nodeSplice(e, itemIndex, targetIndex, item, targetData, table.rows);
item[props.grid.props.foreignKey] = null;
setOrder(table.rows);
//
// updateOrderData.push(...toRaw(table.rows));
} else {
addRecord(e, table.rows, item, targetData, itemIndex, targetIndex);
childrenResetOrder(e, table.rows, item, targetData, itemIndex, targetIndex);
}
});
};

73
io.sc.platform.core.frontend/src/platform/components/grid/WGrid.vue

@ -71,6 +71,7 @@
:get-row="getRow"
:set-old-value="setOldValue"
:no-data-tr-colspan="noDataTrColspanComputed"
:all-ticked-status="allTickedStatus"
></GridBody>
</template>
@ -146,6 +147,7 @@ const props = defineProps({
title: { type: String, default: '' }, //
autoFetchData: { type: Boolean, default: true }, //
draggable: { type: [Boolean, String], default: false }, // true访local
draggableOrderBy: { type: String, default: 'order' }, //
pageable: { type: Boolean, default: true }, //
configButton: { type: Boolean, default: true }, //
dataUrl: { type: String, default: '' }, // URL
@ -170,15 +172,21 @@ const props = defineProps({
treeRelationship: { type: String, default: 'parent' }, // parent, childrenparentchildren
primaryKey: { type: String, default: 'id' }, // APIRestCrudControllerupdate
foreignKey: { type: String, default: 'parent' }, //
orderBy: { type: String, default: 'order' }, //
refreshData: { type: Boolean, default: false }, // primaryKey
dbClickOperation: { type: String, default: 'view' }, // nameclickexpand()
dbClickOperation: { type: String, default: 'view' }, // nameclickexpand()none()
sortBy: {
type: Array,
default: () => {
return [];
},
}, // ['userName', '-lastModifyDate']-
tickedRecord: {
// columnNamedata
type: Object,
default: () => {
return {};
},
},
queryCriteria: {
//
type: Object,
@ -286,6 +294,8 @@ const emit = defineEmits<{
(e: 'rowDbClick', evt: Event, row: any, index: number): void;
// checkbox
(e: 'updateTicked', evt: Event, row: any): void;
// checkbox
(e: 'updateTickeds', value: boolean): void;
(
e: 'beforeRequestData', //
requestParams: URLSearchParams | any, //
@ -578,7 +588,7 @@ const rowClick = (evt: any, row: any, index: any) => {
}
row[table.selectedField] = true;
row[table.tickedField] = true;
bodyRef.value?.allTickedStatus();
allTickedStatus();
if (props.onRowClick) {
emit('rowClick', evt, row, index);
}
@ -590,7 +600,7 @@ const rowDbClick = (evt, row, index) => {
emit('rowDbClick', evt, row, index);
} else if (props.dbClickOperation === 'view') {
viewRef.value?.view();
} else {
} else if (props.dbClickOperation !== 'none') {
topRef.value?.dbClickOperation(row);
}
}
@ -756,6 +766,7 @@ const onRequest = async (ops: any) => {
state.pagination.sortBy = ops.pagination.sortBy;
state.pagination.descending = ops.pagination.descending;
setRowDataExtraProperty(table.rows);
allTickedStatus();
stickyHeaderColumn(100);
emit('afterRequestData', table.rows);
//
@ -764,6 +775,56 @@ const onRequest = async (ops: any) => {
}
};
//
const allTickedStatus = () => {
// null
// false
// true
if (props.checkboxSelection) {
const ticked_ = <any>[];
table.rows.forEach((item) => {
if (item[table.tickedField]) {
ticked_.push(item);
}
});
if (ticked_.length === table.rows.length) {
table.allTicked = true;
} else if (ticked_.length > 0) {
table.allTicked = null;
} else {
table.allTicked = false;
}
}
};
const getInitRowTicked = (rowData) => {
if (rowData[rowDataExtraPropertyName.ticked]) {
return true;
} else if (Object.keys(props.tickedRecord).length > 0 && props.tickedRecord['data']?.length > 0) {
const columnName = props.tickedRecord['columnName'] || props.primaryKey;
const data = props.tickedRecord['data'];
if (
data.findIndex((item) => {
return item === rowData[columnName];
}) > -1
) {
return true;
}
}
return false;
};
const getInitRowSelected = (rowData) => {
if (rowData[rowDataExtraPropertyName.selected]) {
return true;
} else if (!props.tree && rowData[rowDataExtraPropertyName.ticked]) {
return true;
} else if (props.tree && !props.checkboxSelection && rowData[rowDataExtraPropertyName.ticked]) {
return true;
} else {
return false;
}
};
/**
* 行数据附加属性名称
*/
@ -782,8 +843,8 @@ const rowDataExtraPropertyName = {
*/
const initRowDataExtraProperty = (rowData) => {
rowData[rowDataExtraPropertyName.rowKey] = Tools.uuid();
rowData[rowDataExtraPropertyName.ticked] = rowData[rowDataExtraPropertyName.ticked] || false;
rowData[rowDataExtraPropertyName.selected] = rowData[rowDataExtraPropertyName.selected] || false;
rowData[rowDataExtraPropertyName.ticked] = getInitRowTicked(rowData);
rowData[rowDataExtraPropertyName.selected] = getInitRowSelected(rowData);
rowData[rowDataExtraPropertyName.rowOldValue] = {};
setOldValue(rowData);
if (props.tree) {

2
io.sc.platform.core.frontend/src/platform/components/grid/ts/grid.ts

@ -57,3 +57,5 @@ export const columnDefaultProps = (columns: any, sortNo: boolean = true) => {
}
return [];
};
export const draggableImage = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' %3E%3Cpath /%3E%3C/svg%3E`;

Loading…
Cancel
Save