Browse Source

表格组件优化与缺陷修复

main
likunming 3 months ago
parent
commit
3bd47589a6
  1. 393
      io.sc.platform.core.frontend/src/platform/components/grid/Header.vue
  2. 23
      io.sc.platform.core.frontend/src/platform/components/grid/WGrid.vue
  3. 4
      io.sc.platform.core.frontend/src/platform/components/grid/css/grid.css
  4. 21
      io.sc.platform.core.frontend/src/platform/components/grid/css/separator.css
  5. 4
      io.sc.platform.core.frontend/src/platform/components/grid/css/sticky.css
  6. 21
      io.sc.platform.core.frontend/src/platform/components/grid/extra/config/ConfigPanel.vue
  7. 36
      io.sc.platform.core.frontend/src/platform/components/grid/extra/config/DisplayColumn.vue
  8. 1
      io.sc.platform.core.frontend/src/platform/components/grid/extra/config/Separator.vue
  9. 28
      io.sc.platform.core.frontend/src/platform/components/grid/ts/GridTools.ts
  10. 49
      io.sc.platform.core.frontend/src/platform/components/grid/ts/Init.ts
  11. 8
      io.sc.platform.core.frontend/src/platform/components/grid/ts/computed/ComputedManager.ts
  12. 345
      io.sc.platform.core.frontend/src/platform/components/grid/ts/function/ColumnTitle.ts
  13. 20
      io.sc.platform.core.frontend/src/platform/components/grid/ts/function/Operator.ts
  14. 12
      io.sc.platform.core.frontend/src/platform/components/grid/ts/types/table/StoreType.ts

393
io.sc.platform.core.frontend/src/platform/components/grid/Header.vue

@ -1,12 +1,11 @@
<template>
<template v-if="columnTitleState.columnTitleRowNum > 1">
<template v-if="tools.titleFM.rowNum.value > 1">
<!-- 多表头 -->
<q-tr v-for="(r, rIndex) in columnTitleState.columnTitleArr" :key="rIndex">
<q-tr v-for="(r, rIndex) in tools.titleFM.multiTitles.value" :key="rIndex">
<q-th
v-if="rIndex === 0 && tools.table.configStore.useCheckboxSelection && !tools.props.tree && tools.props.selectMode !== Constant.SELECT_MODE.NONE"
:rowspan="columnTitleState.columnTitleRowNum"
:style="moreColumnTitleTableSelectionStyle"
class="firstColumn"
:rowspan="tools.titleFM.rowNum.value"
:style="checkboxThStyleComputed"
>
<q-checkbox
v-model="tools.table.store.headerTicked"
@ -15,22 +14,17 @@
@update:model-value="tools.em.updateTickeds(tools.table.store.headerTicked)"
/>
</q-th>
<q-th
v-if="rIndex === 0 && tools.table.configStore.showSortNo && !tools.props.tree"
:rowspan="columnTitleState.columnTitleRowNum"
:style="moreColumnTitleTableSortNoStyle"
:class="rowNumIsFirstColumnComputed ? 'firstColumn' : ''"
>
<q-th v-if="rIndex === 0 && tools.table.configStore.showSortNo && !tools.props.tree" :rowspan="tools.titleFM.rowNum.value" :style="sortNoThStyleComputed">
{{ $t('rownum') }}
</q-th>
<q-th
v-for="(c, cIndex) in r"
v-for="c in r"
:key="c.name"
:rowspan="c.rowspan"
:colspan="c.colspan"
:style="thStyleHandler(c, props.scope)"
:class="c.classes ? c.classes + ' ' + isFirstColumn(c, cIndex) + ' ' + scrollClass(c.name) : ' ' + isFirstColumn(c, cIndex) + ' ' + scrollClass(c.name)"
:props="titleScopeHandler(c, props.scope)"
:style="thStyleHandler(c)"
:class="c.classes"
:props="tools.titleFM.multiTitleThPropsHandle(c, props.scope)"
style="font-weight: bold"
:title="c.title"
>
@ -60,7 +54,7 @@
<q-th
:props="scope"
:style="col.style + (col.name === '_sortNo_' ? 'padding: 0; min-width: 50px;width: 50px;max-width:50px' : '')"
:class="col.classes + scrollClass(col.name)"
:class="col.classes"
style="font-weight: bold"
:title="col.title"
>
@ -76,8 +70,8 @@
</template>
</template>
<script setup lang="ts">
import { Tools, $t } from '@/platform';
import { computed, inject, reactive } from 'vue';
import { $t, Tools } from '@/platform';
import { computed, inject } from 'vue';
import { Constant, GridTools } from './ts/index';
const tools = <GridTools>inject('tools');
@ -91,158 +85,17 @@ const props = defineProps({
},
});
//
const columnTitleState = reactive({
columnTitleRowNum: 1, //
columnTitleArr: <any>[], //
});
//
type MoreColumnTitleType = {
name: string; // name
title: string; // title()
label: string; // label
parentLevel: number; //
childrenLevel: number; //
rowspan: number; //
colspan: number; //
rowIndex: number; //
style: any; //
classes: any; // classes
parents: any; // name
};
// map
let moreColumnTitleMap = new Map<string, MoreColumnTitleType>();
let allColumnMap = new Map();
const scrollClass = (columnName: string) => {
if (tools.table.store.scrollLeft > 0) {
const lastColumn = tools.table.originalColumns[tools.props.stickyNum - 1];
const multiHeaderLastColumn = extractLastColumn(lastColumn);
if (multiHeaderLastColumn.length > 0 && multiHeaderLastColumn.includes(columnName)) {
//
return 'scroll-border-right';
} else if (lastColumn['name'] === columnName) {
return 'scroll-border-right';
}
}
return '';
};
const moreColumnTitleTableSelectionStyle = computed(() => {
if (tools.table.configStore.stickyNum > 0) {
if (tools.props.tree) {
return 'z-index: 3;position: sticky;left: 0px;';
} else {
return 'z-index: 3;position: sticky;left: 0px;padding: 0; width: 50px;min-width:50px;max-width:50px;';
}
}
return 'padding: 0; width: 50px;min-width:50px;max-width:50px;';
});
const moreColumnTitleTableSortNoStyle = computed(() => {
if (tools.table.configStore.useCheckboxSelection && tools.table.configStore.showSortNo && tools.table.configStore.stickyNum > 0) {
return 'font-weight: bold;z-index: 3;position: sticky;left: var(--columnWidth-1-1);width: 50px;min-width:50px;max-width:50px;';
} else if (tools.table.configStore.showSortNo && tools.table.configStore.stickyNum > 0) {
return 'font-weight: bold;z-index: 3;position: sticky;left: 0px;width: 50px;min-width:50px;max-width:50px;';
}
return 'font-weight: bold;width: 50px;min-width:50px;max-width:50px;';
});
const rowNumIsFirstColumnComputed = computed(() => {
if (tools.table.configStore.useCheckboxSelection && !tools.props.tree && tools.props.selectMode !== Constant.SELECT_MODE.NONE) {
return false;
} else if (tools.table.configStore.showSortNo && !tools.props.tree) {
return true;
} else {
return false;
}
});
const isFirstColumn = (c, cIndex) => {
if (tools.table.configStore.useCheckboxSelection && !tools.props.tree && tools.props.selectMode !== Constant.SELECT_MODE.NONE) {
return '';
} else if (tools.table.configStore.showSortNo && !tools.props.tree) {
return '';
} else if (isFirstHandle(c)) {
return 'firstColumn';
} else {
return '';
}
};
const isFirstHandle = (c) => {
let isFirst = false;
const firstColumn = tools.table.originalColumns[0];
if (firstColumn?.name === c['name']) {
isFirst = true;
} else if (firstColumn?.columns?.length > 0) {
isFirst = child(firstColumn.columns, c);
}
return isFirst;
};
const child = (arr, c) => {
let isExist = false;
arr.forEach((item) => {
if (item['name'] === c['name']) {
isExist = true;
} else if (item.columns?.length > 0) {
isExist = child(item.columns, c);
}
});
return isExist;
};
const handlerStickyChildrenColumn = (item, columns) => {
columns.push(item);
if (item.columns && item.columns.length > 0) {
item.columns.forEach((children) => {
handlerStickyChildrenColumn(children, columns);
});
}
};
const getStickyColumn = () => {
const columns = tools.table.originalColumns.filter((item, index) => {
return index < tools.table.configStore.stickyNum;
});
const arr = [];
columns.forEach((item) => {
handlerStickyChildrenColumn(item, arr);
});
return arr;
};
//
const getMoreRowColumnTitleIndex = (name: any) => {
let trIndex = -1;
let tdIndex = -1;
for (let tr = 0; tr < columnTitleState.columnTitleArr.length; tr++) {
const tdArr = columnTitleState.columnTitleArr[tr];
let flag = false;
for (let td = 0; td < tdArr.length; td++) {
if (name === tdArr[td].name) {
trIndex = tr + 1;
tdIndex = td + 1;
flag = true;
break;
}
}
if (flag) {
break;
}
}
return { trIndex: trIndex, tdIndex: tdIndex };
};
const thStickyLastNameComputed = computed(() => {
let result = <any>[];
const stickyColumnArr = getStickyColumn();
const lastColumn = getMoreRowColumnTitleIndex(stickyColumnArr[stickyColumnArr.length - 1]['name']);
const stickyColumnArr = tools.titleFM.getStickyColumn();
const lastColumn = tools.titleFM.getMultiTitleIndex(stickyColumnArr[stickyColumnArr.length - 1]['name']);
if (lastColumn.trIndex === 1) {
// 1
result = [stickyColumnArr[stickyColumnArr.length - 1]];
} else {
const map = new Map();
stickyColumnArr.forEach((item) => {
const trtdIndex = getMoreRowColumnTitleIndex(item['name']);
const trtdIndex = tools.titleFM.getMultiTitleIndex(item['name']);
if (map.has(trtdIndex.trIndex) && map.get(trtdIndex.trIndex)[0] < trtdIndex.tdIndex) {
map.set(trtdIndex.trIndex, [trtdIndex.tdIndex, item]);
} else if (!map.has(trtdIndex.trIndex)) {
@ -256,20 +109,7 @@ const thStickyLastNameComputed = computed(() => {
return result;
});
//
const extractLastColumn = (column: any) => {
const columns = <any>[];
if (column.columns) {
columns.push(column.columns[column.columns.length - 1]['name']);
const childrenResult = extractLastColumn(column.columns);
if (childrenResult.length > 0) {
columns.concat(childrenResult);
}
}
return columns;
};
const thStyleHandler = (c: any, scope: any) => {
const thStyleHandler = (c: any) => {
let style = '';
if (!Tools.isEmpty(c.style)) {
if (c.style.substr(-1) !== ';') {
@ -277,20 +117,19 @@ const thStyleHandler = (c: any, scope: any) => {
}
style = c.style;
}
const stickyColumnArr = getStickyColumn();
if (
tools.table.configStore.stickyNum > 0 &&
stickyColumnArr.findIndex((item: any) => {
tools.titleFM.stickyColumn.value.findIndex((item: any) => {
return item.name === c.name;
}) > -1
) {
const stickyThArr = <any>[];
const trtdIndex = getMoreRowColumnTitleIndex(c.name);
const trtdIndex = tools.titleFM.getMultiTitleIndex(c.name);
if (c.parents && c.parents.length > 0) {
//
// parenttdIndex
for (let tr = 0; tr < trtdIndex.trIndex; tr++) {
const tdArr = columnTitleState.columnTitleArr[tr];
const tdArr = tools.titleFM.multiTitles.value[tr];
for (let td = 0; td < trtdIndex.tdIndex - 1; td++) {
if (tdArr[td] && tdArr[td].parents && tdArr[td].parents.length > 0) {
const result =
@ -308,20 +147,20 @@ const thStyleHandler = (c: any, scope: any) => {
}
}
c.parents.forEach((parent) => {
const parentTrtdIndex = getMoreRowColumnTitleIndex(parent);
if (moreColumnTitleMap.get(parent)!.parents && moreColumnTitleMap.get(parent)!.parents.length > 0) {
const tdArr = columnTitleState.columnTitleArr[parentTrtdIndex.trIndex - 1];
const parentTrtdIndex = tools.titleFM.getMultiTitleIndex(parent);
if (tools.titleFM.multiTitleMap.get(parent)!.parents && tools.titleFM.multiTitleMap.get(parent)!.parents.length > 0) {
const tdArr = tools.titleFM.multiTitles.value[parentTrtdIndex.trIndex - 1];
for (let td = 0; td < parentTrtdIndex.tdIndex - 1; td++) {
const result =
tdArr[td].parents.length === moreColumnTitleMap.get(parent)!.parents.length &&
tdArr[td].parents.every((a) => moreColumnTitleMap.get(parent)!.parents.some((b) => a === b)) &&
moreColumnTitleMap.get(parent)!.parents.every((_b) => tdArr[td].parents.some((_a) => _a === _b));
tdArr[td].parents.length === tools.titleFM.multiTitleMap.get(parent)!.parents.length &&
tdArr[td].parents.every((a) => tools.titleFM.multiTitleMap.get(parent)!.parents.some((b) => a === b)) &&
tools.titleFM.multiTitleMap.get(parent)!.parents.every((_b) => tdArr[td].parents.some((_a) => _a === _b));
if (result) {
stickyThArr.push({ trIndex: parentTrtdIndex.trIndex, tdIndex: td + 1 });
}
}
} else {
const tdArr = columnTitleState.columnTitleArr[parentTrtdIndex.trIndex - 1];
const tdArr = tools.titleFM.multiTitles.value[parentTrtdIndex.trIndex - 1];
for (let td = 0; td < parentTrtdIndex.tdIndex - 1; td++) {
if (
tdArr[td].parents &&
@ -383,172 +222,36 @@ const thStyleHandler = (c: any, scope: any) => {
}
}
}
if (c['name'] === tools.table.columns[tools.table.columns.length - 1]['name']) {
style += 'border-right-width: 0px;';
}
// if (c['name'] === tools.table.columns[tools.table.columns.length - 1]['name']) {
// style += 'border-right-width: 0px;';
// }
return style;
};
const titleScopeHandler = (column: any, scope: any) => {
if (tools.table.columns?.length > 0) {
const i = tools.table.columns.findIndex((item: any) => item['name'] === column.name);
if (i > -1) {
return scope;
}
}
return undefined;
};
// map
const columnToMap = (column: any) => {
if (Tools.isEmpty(column.name)) {
column.name = Tools.uuid();
}
if (Tools.isEmpty(column.label)) {
column.label = column.name;
}
if (column && column.columns && column.columns.length > 0) {
allColumnMap.set(column.name, column);
moreColumnTitleMap.set(column.name, {
name: column.name,
label: column.label,
title: column.title,
parentLevel: 0,
childrenLevel: 0,
rowspan: 0,
colspan: 0,
rowIndex: 0,
style: column.style,
classes: column.classes,
parents: [],
});
column.columns.forEach((item) => {
columnToMap(item);
});
} else {
allColumnMap.set(column.name, column);
moreColumnTitleMap.set(column.name, {
name: column.name,
label: column.label,
title: column.title,
parentLevel: 0,
childrenLevel: 0,
rowspan: 0,
colspan: 0,
rowIndex: 0,
style: column.style,
classes: column.classes,
parents: [],
});
}
};
let tmpColspan = 0;
const deepArr = [];
const getChildrenLevel = (column, deep, deepArr) => {
if (column && column.columns && column.columns.length > 0) {
deep++;
column.columns.forEach((item) => {
getChildrenLevel(item, deep, deepArr);
});
} else if (column) {
// deep++;
deepArr.push(deep);
tmpColspan += 1;
}
};
// ,
function findParents(arrData: any, name: any) {
if (arrData.length == 0) return;
for (let i = 0; i < arrData.length; i++) {
if (arrData[i].name == name) {
return [];
/**
* 复选框 th 样式
*/
const checkboxThStyleComputed = computed(() => {
if (tools.table.configStore.stickyNum > 0) {
if (tools.props.tree) {
return 'z-index: 3;position: sticky;left: 0px;';
} else {
if (arrData[i].columns) {
const res = findParents(arrData[i].columns, name);
if (res !== undefined) {
return res.concat(arrData[i].name).reverse();
}
}
}
}
}
const handlerMoreRowColumnTitle = () => {
moreColumnTitleMap = new Map<string, MoreColumnTitleType>();
allColumnMap = new Map();
tools.table.originalColumns.forEach((tableColumn: any) => {
columnToMap(tableColumn);
});
let maxColumnChildrenLevel = 0;
for (let key of allColumnMap.keys()) {
// childrenLevel
let tmpChildrenLevel = 0;
getChildrenLevel(allColumnMap.get(key), 0, deepArr);
tmpChildrenLevel = Math.max(...deepArr);
if (tmpChildrenLevel > maxColumnChildrenLevel) {
maxColumnChildrenLevel = tmpChildrenLevel;
return 'z-index: 3;position: sticky;left: 0px;padding: 0; width: 50px;min-width:50px;max-width:50px;';
}
deepArr.splice(0, deepArr.length);
moreColumnTitleMap.get(key)!.childrenLevel = tmpChildrenLevel;
// colspan
moreColumnTitleMap.get(key)!.colspan = tmpColspan;
tmpColspan = 0;
// parent
const parent = findParents(tools.table.originalColumns, key);
moreColumnTitleMap.get(key)!.parents = parent;
// parentLevel
moreColumnTitleMap.get(key)!.parentLevel = parent.length;
// rowIndex
moreColumnTitleMap.get(key)!.rowIndex = parent.length;
}
if (maxColumnChildrenLevel > 0) {
// = + 1
columnTitleState.columnTitleRowNum = maxColumnChildrenLevel + 1;
}
return 'padding: 0; width: 50px;min-width:50px;max-width:50px;';
});
const map = new Map<number, [any]>();
for (let key of moreColumnTitleMap.keys()) {
const value: any = moreColumnTitleMap.get(key);
// rowspan
if (value.parentLevel === 0 && value.childrenLevel === 0) {
value.rowspan = columnTitleState.columnTitleRowNum;
} else if (value.parentLevel === 0) {
value.rowspan = 1;
} else {
// - -
value.rowspan = columnTitleState.columnTitleRowNum - value.parentLevel - value.childrenLevel;
}
if (map.has(value.rowIndex)) {
(map.get(value.rowIndex) as any).push(value);
} else {
map.set(value.rowIndex, [value]);
}
/**
* 序号 th 样式
*/
const sortNoThStyleComputed = computed(() => {
if (tools.table.configStore.useCheckboxSelection && tools.table.configStore.showSortNo && tools.table.configStore.stickyNum > 0) {
return 'font-weight: bold;z-index: 3;position: sticky;left: var(--columnWidth-1-1);width: 50px;min-width:50px;max-width:50px;';
} else if (tools.table.configStore.showSortNo && tools.table.configStore.stickyNum > 0) {
return 'font-weight: bold;z-index: 3;position: sticky;left: 0px;width: 50px;min-width:50px;max-width:50px;';
}
const arr = Array.from(map);
columnTitleState.columnTitleArr = [];
arr.sort((a, b) => a[0] - b[0]);
arr.forEach((item) => {
columnTitleState.columnTitleArr.push(item[1]);
});
};
const getColumnTitleState = () => {
return columnTitleState;
};
const getMoreColumnTitleMap = () => {
return moreColumnTitleMap;
};
defineExpose({
handlerMoreRowColumnTitle,
getColumnTitleState,
getMoreColumnTitleMap,
return 'font-weight: bold;width: 50px;min-width:50px;max-width:50px;';
});
</script>

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

@ -102,8 +102,9 @@ watch(
(newVal, oldVal) => {
tools.table.originalColumns = newVal;
tools.table.columns = Init.initColumn(tools.table.originalColumns, props);
tools.table.store.multiHeaderLastNames = Init.getMultiHeaderLastNames(props.columns);
if (tools.table.columns && tools.table.columns.length > tools.table.originalColumns.length) {
headerRef.value?.handlerMoreRowColumnTitle();
tools.titleFM.handleColumnTitle();
}
},
);
@ -155,24 +156,11 @@ watchEffect(() => {
}
});
const addScrollListener = () => {
const tableMiddle = getTableRef().$el.getElementsByClassName('q-table__middle')[0];
tableMiddle?.addEventListener('scroll', () => {
tools.table.store.scrollLeft = tableMiddle.scrollLeft;
tools.table.store.scrollTop = tableMiddle.scrollTop;
// TD
if (tools.table.store.scrollLeft > 0) {
//
}
});
};
//
onMounted(() => {
tools.opFM.propsValidate();
if (tools.table.columns?.length > tools.table.originalColumns?.length) {
headerRef.value?.handlerMoreRowColumnTitle();
tools.titleFM.handleColumnTitle();
}
topRef.value?.handleQueryFormShowField();
if (props.autoFetchData && (!Tools.isEmpty(props.dataUrl) || !Tools.isEmpty(props.fetchDataUrl))) {
@ -182,11 +170,6 @@ onMounted(() => {
} else {
tools.opFM.resetStyleVariableValue();
}
//
if (tools.props.stickyNum > 0) {
addScrollListener();
}
});
const getTableRef = () => {

4
io.sc.platform.core.frontend/src/platform/components/grid/css/grid.css

@ -1,8 +1,8 @@
.w-grid .cellSelected {
background-color: rgba(0, 0, 0, .06);
background-color: #f0f0f0 !important;
}
.w-grid .cellHover:hover {
background-color: rgba(0, 0, 0, .03);
background-color: #f7f7f7;
}
.w-grid .mouseDisabled {
cursor: not-allowed; /* 将鼠标指针变为禁用图标 */

21
io.sc.platform.core.frontend/src/platform/components/grid/css/separator.css

@ -37,16 +37,18 @@
}
/*
* vertical
* 多表头左侧边框
* 边框
*/
.w-grid .q-table--vertical-separator th {
border-left: 1px solid rgba(0, 0, 0, 0.12);
}
.w-grid .q-table--vertical-separator th:first-child.firstColumn {
border-left: 0;
border-right: 1px solid rgba(0, 0, 0, 0.12);
}
.w-grid .q-table--vertical-separator th:first-child:not([rowspan]) {
.w-grid .q-table--vertical-separator tbody tr td {
border-left: 0;
border-right: 1px solid rgba(0, 0, 0, 0.12);
}
.w-grid .q-table--vertical-separator tbody tr td:last-child {
border-right: 0;
}
/*
* vertical
@ -66,16 +68,15 @@
}
/*
* cell
* 多表头左侧边框
* 边框
*/
.w-grid .q-table--cell-separator th {
border-left: 1px solid rgba(0, 0, 0, 0.12);
}
.w-grid .q-table--cell-separator th:first-child.firstColumn {
border-left: 0;
border-right: 1px solid rgba(0, 0, 0, 0.12);
}
.w-grid .q-table--cell-separator th:first-child:not([rowspan]) {
.w-grid .q-table--cell-separator tbody tr td {
border-left: 0;
border-right: 1px solid rgba(0, 0, 0, 0.12);
}
/*
* cell

4
io.sc.platform.core.frontend/src/platform/components/grid/css/sticky.css

@ -1,7 +1,3 @@
.scroll-border-right {
border-right: 1px solid rgba(0, 0, 0, 0.12) !important;
}
.sticky-header-column-table thead tr:not(.noDataTr) {
height: var(--tableColumnTitleHeight) !important;
}

21
io.sc.platform.core.frontend/src/platform/components/grid/extra/config/ConfigPanel.vue

@ -1,5 +1,5 @@
<template>
<q-list padding style="min-width: 250px">
<q-list padding :style="styleComputed">
<!-- 全屏 -->
<Fullscreen :scope="props.scope"></Fullscreen>
<q-separator />
@ -33,11 +33,12 @@
<q-separator />
<!-- 显示列 -->
<DisplayColumn></DisplayColumn>
<DisplayColumn v-model="displayColumnOpen"></DisplayColumn>
</q-list>
</template>
<script setup lang="ts">
import { inject } from 'vue';
import { inject, computed, ref } from 'vue';
import { useQuasar } from 'quasar';
import { Constant, GridTools } from '../../ts/index';
import CheckboxSelection from './CheckboxSelection.vue';
import Dense from './Dense.vue';
@ -48,6 +49,7 @@ import SortNo from './SortNo.vue';
import StickyColumn from './StickyColumn.vue';
import AloneGorup from './AloneGroup.vue';
const $q = useQuasar();
const tools = <GridTools>inject('tools');
const props = defineProps({
scope: {
@ -57,4 +59,17 @@ const props = defineProps({
},
},
});
const displayColumnOpen = ref(false);
const styleComputed = computed(() => {
//
const defaultStyle = 'min-width: 250px;';
let style = defaultStyle;
if (displayColumnOpen.value) {
style = defaultStyle + 'height: ' + ($q.screen.height - $q.screen.height * 0.3) + 'px;';
} else {
style = defaultStyle;
}
return style;
});
</script>

36
io.sc.platform.core.frontend/src/platform/components/grid/extra/config/DisplayColumn.vue

@ -1,22 +1,14 @@
<template>
<q-expansion-item label="显示列">
<q-expansion-item v-model="modelValue" label="显示列">
<q-card>
<q-card-section>
<template v-for="col in tools.table.columns" :key="col.name">
<q-item v-if="showColumn(col.name)" v-ripple tag="label" dense>
<q-item-section side>
<q-checkbox
v-model="col.showIf"
dense
@update:model-value="
() => {
tools.opFM.resetStyleVariableValue(100);
}
"
/>
<q-checkbox v-model="col.showIf" dense @update:model-value="updateModelValue" />
</q-item-section>
<q-item-section>
<q-item-label>{{ col.label || col.name }}</q-item-label>
<q-item-label>{{ col.label + '_' + col.name }}</q-item-label>
</q-item-section>
</q-item>
</template>
@ -26,22 +18,18 @@
</template>
<script setup lang="ts">
import { inject } from 'vue';
import { GridTools } from '../../ts/index';
import { GridTools, Constant } from '../../ts/index';
const modelValue = defineModel<boolean>();
const tools = <GridTools>inject('tools');
const showColumn = (name) => {
// if (props.moreColumnTitleArray.length > 0) {
// if (
// props.moreColumnTitleArray[0].findIndex((item) => {
// return item.name === name;
// }) > -1
// ) {
// return true;
// } else {
// return false;
// }
// } else {
// }
if (name === Constant.FIELD_NAMES.SORT_NO) {
return false;
}
return true;
};
const updateModelValue = () => {
tools.titleFM.handleColumnTitle();
tools.opFM.resetStyleVariableValue(0);
};
</script>

1
io.sc.platform.core.frontend/src/platform/components/grid/extra/config/Separator.vue

@ -83,6 +83,7 @@ const separatorChange = (separator, value) => {
} else {
tools.table.configStore.separator = separator;
}
tools.opFM.resetStyleVariableValue(100);
};
watch(

28
io.sc.platform.core.frontend/src/platform/components/grid/ts/GridTools.ts

@ -16,6 +16,7 @@ import { Operator } from './function/Operator';
import { RequestApi } from './function/RequestApi';
import { RowData } from './function/RowData';
import { ButtonManager } from './toolbar/ButtonManager';
import { ColumnTitle } from './function/ColumnTitle';
/**
* w-grid
@ -74,6 +75,10 @@ export class GridTools {
* criteria
*/
criteriaFM: Criteria;
/**
*
*/
titleFM: ColumnTitle;
constructor(props: PropsType) {
this.props = props;
@ -99,9 +104,23 @@ export class GridTools {
}
}
const columns = Init.initColumn(props.columns, props);
/**
*
* [
* { name: 'a', columns: [{ name: 'a1' },{ name: 'a2', columns: [{ name: 'a21' }, {name: 'a22'}]}] },
* { name: 'b', columns: [{ name: 'b1' },{ name: 'b2'}] },
* { name: 'c' }
* ]
* { a: ['a2','a22'], b: ['b2'] }
*
*/
const multiHeaderLastNames = Init.getMultiHeaderLastNames(props.columns);
this.table = reactive({
originalColumns: [...props.columns],
columns: Init.initColumn(props.columns, props),
columns: columns,
rows: [],
queryCriteria: { ...props.queryCriteria },
moreQueryStatus: false,
@ -161,8 +180,9 @@ export class GridTools {
loadingLabelI18nKey: 'tip.dataLoading',
bodyCellHeight: bodyCellHeight,
resizeFlag: false,
scrollLeft: 0,
scrollTop: 0,
multiHeaderLastNames: multiHeaderLastNames,
existXScroll: false,
existYScroll: false,
location: {
yLocation: 0,
noDataTrYLocation: 0,
@ -206,6 +226,7 @@ export class GridTools {
this.dndFM = new DragAndDrop(props, this.table);
this.reqApiFM = new RequestApi(props, this.table);
this.criteriaFM = new Criteria(props, this.table);
this.titleFM = new ColumnTitle(props, this.table);
}
// 设置组件实例
@ -222,6 +243,7 @@ export class GridTools {
this.dndFM.setInstance(instance, this_);
this.reqApiFM.setInstance(instance, this_);
this.criteriaFM.setInstance(instance, this_);
this.titleFM.setInstance(instance, this_);
}
/**

49
io.sc.platform.core.frontend/src/platform/components/grid/ts/Init.ts

@ -200,9 +200,9 @@ export class Init {
const gridColumns = <any>[];
if (columns && columns.length > 0) {
const sortable = true;
gridColumns.push({ name: '_sortNo_', align: 'center', label: $t('rownum'), field: '_sortNo_', showIf: props.sortNo });
gridColumns.push({ name: '_sortNo_', align: 'center', label: $t('rownum'), field: '_sortNo_', sortable: false, showIf: props.sortNo });
columns.forEach((item: any) => {
this.childrenHandler(item, gridColumns, sortable);
Init.childrenHandler(item, gridColumns, sortable);
});
return gridColumns;
}
@ -213,10 +213,10 @@ export class Init {
private static childrenHandler(item: any, gridColumns: any, sortable: boolean) {
if (item.columns && item.columns.length > 0) {
item.columns.forEach((column) => {
this.childrenHandler(column, gridColumns, sortable);
Init.childrenHandler(column, gridColumns, sortable);
});
} else {
this.columnStyle(item);
Init.columnStyle(item);
const col = {
...{ align: 'left', label: item.name, field: item.name, name: item.name, sortable: sortable, showIf: true },
...item,
@ -249,4 +249,45 @@ export class Init {
}
}
}
// 获得多表头子列每层最后一列的名字集合
public static getMultiHeaderLastNames(columns: any) {
const multiHeaderLastNames = {};
if (columns && columns.length > 0) {
columns.forEach((item: any) => {
if (item.columns && item.columns.length > 0) {
const names = [];
Init.extractLastColumn(names, item.columns);
multiHeaderLastNames[item['name']] = names;
}
});
}
return multiHeaderLastNames;
}
// 根据列获取最后一层的最后一列的名字
public static getStickyLastName(column) {
// 如果对象没有columns属性,直接返回name
if (!column.columns) {
return column.name;
}
let currentLayer = column;
while (currentLayer.columns && currentLayer.columns.length > 0) {
// 更新当前层为最后一列的对象
currentLayer = currentLayer.columns[currentLayer.columns.length - 1];
}
return currentLayer.name;
}
// 提取多表头每一层级的最后一列
private static extractLastColumn = (names: any, columns: any) => {
const lastElement = columns[columns.length - 1];
names.push(lastElement.name);
// 如果最后一个元素有子列,则递归调用该函数
if (lastElement.columns && lastElement.columns.length > 0) {
Init.extractLastColumn(names, lastElement.columns);
}
};
}

8
io.sc.platform.core.frontend/src/platform/components/grid/ts/computed/ComputedManager.ts

@ -163,14 +163,12 @@ export class ComputedManager extends Base {
tableClass = computed(() => {
const classArr = ['sticky-header-column-table', 'w-grid'];
if (this.table.configStore.stickyNum && this.table.configStore.stickyNum > 0) {
if (this.table.componentRef.getHeaderRef()?.getColumnTitleState()?.columnTitleRowNum > 1) {
if (this.tools?.titleFM.rowNum.value > 1) {
// 存在多行列头
const stickyColumn = this.table.originalColumns.filter((item, index) => {
return index < this.table.configStore.stickyNum;
});
const stickyColumn = this.tools?.titleFM.stickyTopColumn.value;
let tdNum = this.extColumnNum.value;
stickyColumn.forEach((item: any, index: number) => {
tdNum += this.table.componentRef.getHeaderRef()?.getMoreColumnTitleMap()?.get(item.name)?.colspan;
tdNum += this.tools?.titleFM?.multiTitleMap?.get(item.name)?.colspan;
});
// 处理表格数据列锁定
if (tdNum > 0) {

345
io.sc.platform.core.frontend/src/platform/components/grid/ts/function/ColumnTitle.ts

@ -0,0 +1,345 @@
import { computed, Ref, ref, toRaw } from 'vue';
import { Tools } from '@/platform';
import { Base } from '../Base';
import { Constant, PropsType, TableType } from '../index';
/**
* w-grid
*/
export class ColumnTitle extends Base {
/**
* > 1
*/
rowNum: Ref = ref(1);
/**
*
* [
* [{name: 'industry', label: '行业分类', colspan: 2}, {name: 'company', label: '公司名称', rowspan: 2}],
* [{name: 'industry2', label: '二级行业'}, {name: 'industry3', label: '三级行业'}],
* ]
*/
multiTitles: Ref = ref([]);
/**
* key的Map对象
*/
multiTitleMap: Map<string, MultiTitleType> = new Map<string, MultiTitleType>();
/**
* key的Map对象
*/
originalColumnMap: Map<string, any> = new Map();
/**
*
*/
stickyColumn: Ref = ref([]);
/**
*
*/
stickyTopColumn: Ref = ref([]);
/**
*
*/
private childrenMaxLevel: number = 0;
/**
*
*/
private colspan: number = 0;
constructor(props: PropsType, table: TableType) {
super(props, table);
this.handleColumnTitle = this.handleColumnTitle.bind(this);
this.multiTitleThPropsHandle = this.multiTitleThPropsHandle.bind(this);
this.showIfIsFalse = this.showIfIsFalse.bind(this);
this.processColumns = this.processColumns.bind(this);
this.processMap = this.processMap.bind(this);
}
/**
*
*/
handleColumnTitle() {
this.multiTitleMap = new Map<string, MultiTitleType>();
this.originalColumnMap = new Map();
// 深度克隆列,避免被内部操作列覆盖原有数据。
const clonedArray = JSON.parse(JSON.stringify(this.table.originalColumns));
clonedArray.forEach((column: any) => {
this.columnToMap(toRaw(column));
});
this.childrenMaxLevel = 0;
this.colspan = 0;
const deepArr = [];
// 剔除被隐藏的列
this.originalColumnMap = this.processMap(this.originalColumnMap);
this.multiTitleMap = this.removeMultiTitleMap();
for (const key of this.originalColumnMap.keys()) {
// 处理列的 childrenLevel
let tmpChildrenLevel = 0;
this.getChildrenLevel(this.originalColumnMap.get(key), 0, deepArr);
tmpChildrenLevel = Math.max(...deepArr);
if (tmpChildrenLevel > this.childrenMaxLevel) {
this.childrenMaxLevel = tmpChildrenLevel;
}
deepArr.splice(0, deepArr.length);
this.multiTitleMap.get(key)!.childrenLevel = tmpChildrenLevel;
// 处理列的 colspan
this.multiTitleMap.get(key)!.colspan = this.colspan;
this.colspan = 0;
// 处理列的 parent
const parent = this.findParents(this.table.originalColumns, key);
this.multiTitleMap.get(key)!.parents = parent;
// 处理列的 parentLevel
this.multiTitleMap.get(key)!.parentLevel = parent.length;
// 处理列的 rowIndex
this.multiTitleMap.get(key)!.rowIndex = parent.length;
}
if (this.childrenMaxLevel > 0) {
// 更改列头总行数 = 最大子层级数 + 1
this.rowNum.value = this.childrenMaxLevel + 1;
}
const map = new Map<number, [any]>();
for (const key of this.multiTitleMap.keys()) {
const value: any = this.multiTitleMap.get(key);
// 得到列头总行数后处理列的 rowspan
if (value.parentLevel === 0 && value.childrenLevel === 0) {
value.rowspan = this.rowNum.value;
} else if (value.parentLevel === 0) {
value.rowspan = 1;
} else {
// 总行数 - 父层级数 - 子层级数
value.rowspan = this.rowNum.value - value.parentLevel - value.childrenLevel;
}
if (map.has(value.rowIndex)) {
(map.get(value.rowIndex) as any).push(value);
} else {
map.set(value.rowIndex, [value]);
}
}
const arr = Array.from(map);
this.multiTitles.value = [];
arr.sort((a, b) => a[0] - b[0]);
arr.forEach((item) => {
this.multiTitles.value.push(item[1]);
});
this.setStickyColumn();
}
/**
* th props
* @param column
* @param scope
* @returns
*/
multiTitleThPropsHandle(column: any, scope: any) {
if (this.table.columns?.length > 0) {
const i = this.table.columns.findIndex((item: any) => item['name'] === column.name);
if (i > -1) {
return scope;
}
}
return undefined;
}
/**
*
* @param name
* @returns
*/
getMultiTitleIndex(name: any) {
let trIndex = -1;
let tdIndex = -1;
for (let tr = 0; tr < this.multiTitles.value.length; tr++) {
const tdArr = this.multiTitles.value[tr];
let flag = false;
for (let td = 0; td < tdArr.length; td++) {
if (name === tdArr[td].name) {
trIndex = tr + 1;
tdIndex = td + 1;
flag = true;
break;
}
}
if (flag) {
break;
}
}
return { trIndex: trIndex, tdIndex: tdIndex };
}
/**
*
* @returns
*/
getStickyColumn() {
const columns = <any>[];
for (let i = 0; i < this.table.originalColumns.length; i++) {
const column = this.table.originalColumns[i];
if (columns.length < this.table.configStore.stickyNum && this.originalColumnMap.get(column['name'])) {
columns.push(column);
}
}
this.stickyTopColumn.value = columns;
const arr = [];
columns.forEach((item) => {
this.handleStickyChildrenColumn(item, arr);
});
return arr;
}
private setStickyColumn() {
this.stickyColumn.value = this.getStickyColumn();
}
/**
*
* @param item
* @param columns
*/
private handleStickyChildrenColumn(item, columns) {
columns.push(item);
if (item.columns && item.columns.length > 0) {
item.columns.forEach((children) => {
this.handleStickyChildrenColumn(children, columns);
});
}
}
private findParents(arrData: any, name: any) {
if (arrData.length == 0) return;
for (let i = 0; i < arrData.length; i++) {
if (arrData[i].name == name) {
return [];
} else {
if (arrData[i].columns) {
const res = this.findParents(arrData[i].columns, name);
if (res !== undefined) {
return res.concat(arrData[i].name).reverse();
}
}
}
}
}
private getChildrenLevel(column, deep, deepArr) {
if (column && column.columns && column.columns.length > 0) {
deep++;
column.columns.forEach((item) => {
this.getChildrenLevel(item, deep, deepArr);
});
} else if (column) {
deepArr.push(deep);
this.colspan += 1;
}
}
private showIfIsFalse(name: string) {
const column_ = this.table.columns.find((item) => item.name === name);
if (!Tools.isEmpty(column_) && Tools.hasOwnProperty(column_, 'showIf') && column_.showIf === false) {
return true;
}
return false;
}
private processColumns(columns) {
return columns.reduce((acc, column) => {
if (column.columns) {
column.columns = this.processColumns(column.columns);
if (column.columns.length > 0) {
acc.push(column);
}
} else if (!this.showIfIsFalse(column.name)) {
acc.push(column);
}
return acc;
}, []);
}
private processMap(map: Map<string, any>) {
const newMap = new Map();
for (const key of map.keys()) {
const item = map.get(key);
if (item.columns) {
item.columns = this.processColumns(item.columns);
if (item.columns.length > 0) {
newMap.set(key, item);
}
} else if (!this.showIfIsFalse(item.name)) {
newMap.set(key, item);
}
}
return newMap;
}
private removeMultiTitleMap() {
const newMap = new Map();
for (const key of this.multiTitleMap.keys()) {
const value: any = this.multiTitleMap.get(key);
if (this.originalColumnMap.get(key)) {
newMap.set(key, value);
}
}
return newMap;
}
private setMap(column: any) {
this.originalColumnMap.set(column.name, column);
this.multiTitleMap.set(column.name, {
name: column.name,
label: column.label,
title: column.title,
parentLevel: 0,
childrenLevel: 0,
rowspan: 0,
colspan: 0,
rowIndex: 0,
style: column.style,
classes: column.classes,
parents: [],
});
}
private columnToMap(column: any) {
if (Tools.isEmpty(column.name)) {
column.name = Tools.uuid();
}
if (Tools.isEmpty(column.label)) {
column.label = column.name;
}
if (column && column.columns && column.columns.length > 0) {
this.setMap(column);
column.columns.forEach((item) => {
this.columnToMap(item);
});
} else {
this.setMap(column);
}
}
}
/**
*
*/
type MultiTitleType = {
name: string; // 列模型的name
title: string; // 列模型的title(鼠标移动上去显示内容)
label: string; // 列模型的label
parentLevel: number; // 父层级数
childrenLevel: number; // 子层级数
rowspan: number; // 跨行数
colspan: number; // 跨列数
rowIndex: number; // 列模型所处的行下标
style: any; // 列模型配置的内嵌样式
classes: any; // 列模型配置的 classes
parents: any; // 列模型的父name集合
};

20
io.sc.platform.core.frontend/src/platform/components/grid/ts/function/Operator.ts

@ -7,8 +7,6 @@ import { Constant, PropsType, TableType } from '../index';
* w-grid
*/
export class Operator extends Base {
private topResizeFlag = false;
constructor(props: PropsType, table: TableType) {
super(props, table);
this.tableFullscreen = this.tableFullscreen.bind(this);
@ -124,6 +122,11 @@ export class Operator extends Base {
this.table.store.location.middleScrollWidth = tableElement.getElementsByClassName('q-table__middle')[0]?.scrollWidth;
this.table.store.location.columnHeadHeight = tableElement.getElementsByTagName('thead')[0]?.clientHeight;
this.table.store.location.hideHeaderNoDataHeight = tableElement.getElementsByClassName('q-table__middle')[0]?.clientHeight;
const scrollHeight = tableElement.getElementsByClassName('q-table__middle')[0]?.scrollHeight;
if (scrollHeight > this.table.store.location.hideHeaderNoDataHeight) {
// 存在纵向滚动条
this.table.store.existYScroll = true;
}
// 判断是否有数据,没数据修改 middleHeight
if ((this.table.rows && this.table.rows.length > 0) || this.props.hideBottom) {
this.table.store.location.middleHeight = tableElement.getElementsByClassName('q-table__middle')[0]?.clientHeight;
@ -131,6 +134,7 @@ export class Operator extends Base {
let scrollHeight = 0;
if (this.table.store.location.middleScrollWidth - this.table.store.location.middleWidth > 0) {
scrollHeight = 15;
this.table.store.existXScroll = true;
}
this.table.store.location.middleHeight = this.table.store.location.columnHeadHeight + scrollHeight;
}
@ -170,6 +174,13 @@ export class Operator extends Base {
this.table.store.location.middleScrollWidth = tableElement.getElementsByClassName('q-table__middle')[0]?.scrollWidth;
this.table.store.location.columnHeadHeight = tableElement.getElementsByTagName('thead')[0]?.clientHeight;
this.table.store.location.hideHeaderNoDataHeight = tableElement.getElementsByClassName('q-table__middle')[0]?.clientHeight;
const scrollHeight = tableElement.getElementsByClassName('q-table__middle')[0]?.scrollHeight;
if (scrollHeight > this.table.store.location.hideHeaderNoDataHeight) {
// 存在纵向滚动条
this.table.store.existYScroll = true;
} else {
this.table.store.existYScroll = false;
}
// 判断是否有数据,没数据修改 middleHeight
if (this.table?.rows?.length > 0 || this.props.hideBottom) {
this.table.store.location.middleHeight = tableElement.getElementsByClassName('q-table__middle')[0]?.clientHeight;
@ -177,13 +188,16 @@ export class Operator extends Base {
let scrollHeight = 0;
if (this.table.store.location.middleScrollWidth - this.table.store.location.middleWidth > 0) {
scrollHeight = 15;
this.table.store.existXScroll = true;
} else {
this.table.store.existXScroll = false;
}
this.table.store.location.middleHeight = this.table.store.location.columnHeadHeight + scrollHeight;
}
const tableDom = tableElement.getElementsByTagName('table')[0];
if (this.table.configStore.stickyNum > 0) {
if (this.table.componentRef.getHeaderRef()?.getColumnTitleState()?.columnTitleRowNum > 1) {
if (this.tools?.titleFM.rowNum.value > 1) {
const theadDom = tableDom.getElementsByTagName('thead')[0];
const theadTrDom = theadDom.getElementsByTagName('tr');
tableDom.style.setProperty('--columnWidth' + '-' + 0 + '-' + 0, 0 + 'px');

12
io.sc.platform.core.frontend/src/platform/components/grid/ts/types/table/StoreType.ts

@ -75,11 +75,15 @@ export type StoreType = {
*/
pagination: PaginationType,
/**
*
*
*/
scrollLeft: number,
multiHeaderLastNames: any,
/**
*
*
*/
scrollTop: number,
existXScroll: boolean,
/**
*
*/
existYScroll: boolean,
}
Loading…
Cancel
Save