Browse Source

* 组件重构优化。

* 完善树表格拖拽,支持拖拽过程中停留在目标节点1.5秒将其展开;支持将数据拖入至无孩子节点的目标节点中。
* 新增树表格懒加载模式。
* 新增数据分组功能,支持根据分组字段提取独立行或合并分组。
* 新增高级查询功能,支持用户自定义组装查询条件。
* 新增自定义追加行功能。
* 内置按钮新增`fullScreen`,该按钮为全屏与退出动态变化。
* 用户配置中增加分组、分割线配置功能。
* 优化Form表单验证错误,先看是否有errorMessageI18nKey,有的情况下优先使用。
* 修复全屏时紧凑失效与底部边框不显示问题。
* 修复内置编辑窗口中的按钮提交报错后关闭窗口重新打开仍然处于loading状态的问题。
* beforeRemove与beforeRequestData事件增加同步操作,事件方法处理完成后再执行内部处理。
main
likunming 3 months ago
parent
commit
26a5bc3d67
  1. 3
      io.sc.platform.core.frontend/package.json
  2. 5
      io.sc.platform.core.frontend/src/platform/components/form/WForm.vue
  3. 32
      io.sc.platform.core.frontend/src/platform/components/grid/Body.vue
  4. 134
      io.sc.platform.core.frontend/src/platform/components/grid/CellEditor.vue
  5. 27
      io.sc.platform.core.frontend/src/platform/components/grid/GridAppendRow.vue
  6. 376
      io.sc.platform.core.frontend/src/platform/components/grid/GridBody.vue
  7. 363
      io.sc.platform.core.frontend/src/platform/components/grid/GridConfig.vue
  8. 359
      io.sc.platform.core.frontend/src/platform/components/grid/GridEditToolbar.vue
  9. 232
      io.sc.platform.core.frontend/src/platform/components/grid/GridEditor.vue
  10. 73
      io.sc.platform.core.frontend/src/platform/components/grid/GridPagination.vue
  11. 210
      io.sc.platform.core.frontend/src/platform/components/grid/GridTd.vue
  12. 786
      io.sc.platform.core.frontend/src/platform/components/grid/GridTop.vue
  13. 100
      io.sc.platform.core.frontend/src/platform/components/grid/GridView.vue
  14. 171
      io.sc.platform.core.frontend/src/platform/components/grid/Header.vue
  15. 41
      io.sc.platform.core.frontend/src/platform/components/grid/Pagination.vue
  16. 200
      io.sc.platform.core.frontend/src/platform/components/grid/Td.vue
  17. 2
      io.sc.platform.core.frontend/src/platform/components/grid/TdContent.vue
  18. 287
      io.sc.platform.core.frontend/src/platform/components/grid/Top.vue
  19. 117
      io.sc.platform.core.frontend/src/platform/components/grid/Tr.vue
  20. 269
      io.sc.platform.core.frontend/src/platform/components/grid/TreeGridFirstTdContent.vue
  21. 907
      io.sc.platform.core.frontend/src/platform/components/grid/TreeGridRow.vue
  22. 1849
      io.sc.platform.core.frontend/src/platform/components/grid/WGrid.vue
  23. 7
      io.sc.platform.core.frontend/src/platform/components/grid/css/separator.css
  24. 216
      io.sc.platform.core.frontend/src/platform/components/grid/extra/Editor.vue
  25. 71
      io.sc.platform.core.frontend/src/platform/components/grid/extra/ViewPanel.vue
  26. 58
      io.sc.platform.core.frontend/src/platform/components/grid/extra/advanced-query/AdvancedQuery.vue
  27. 21
      io.sc.platform.core.frontend/src/platform/components/grid/extra/append/AppendContent.vue
  28. 39
      io.sc.platform.core.frontend/src/platform/components/grid/extra/append/AppendRow.vue
  29. 54
      io.sc.platform.core.frontend/src/platform/components/grid/extra/config/AloneGroup.vue
  30. 36
      io.sc.platform.core.frontend/src/platform/components/grid/extra/config/CheckboxSelection.vue
  31. 60
      io.sc.platform.core.frontend/src/platform/components/grid/extra/config/ConfigPanel.vue
  32. 57
      io.sc.platform.core.frontend/src/platform/components/grid/extra/config/Dense.vue
  33. 47
      io.sc.platform.core.frontend/src/platform/components/grid/extra/config/DisplayColumn.vue
  34. 35
      io.sc.platform.core.frontend/src/platform/components/grid/extra/config/Fullscreen.vue
  35. 96
      io.sc.platform.core.frontend/src/platform/components/grid/extra/config/Separator.vue
  36. 36
      io.sc.platform.core.frontend/src/platform/components/grid/extra/config/SortNo.vue
  37. 44
      io.sc.platform.core.frontend/src/platform/components/grid/extra/config/StickyColumn.vue
  38. 54
      io.sc.platform.core.frontend/src/platform/components/grid/extra/group/GroupTr.vue
  39. 97
      io.sc.platform.core.frontend/src/platform/components/grid/extra/inline-edit/CellEditor.vue
  40. 71
      io.sc.platform.core.frontend/src/platform/components/grid/extra/inline-edit/InlineEditComponent.vue
  41. 249
      io.sc.platform.core.frontend/src/platform/components/grid/extra/inline-edit/InlineEditToolbar.vue
  42. 48
      io.sc.platform.core.frontend/src/platform/components/grid/ts/Base.ts
  43. 328
      io.sc.platform.core.frontend/src/platform/components/grid/ts/GridTools.ts
  44. 226
      io.sc.platform.core.frontend/src/platform/components/grid/ts/Init.ts
  45. 252
      io.sc.platform.core.frontend/src/platform/components/grid/ts/computed/ComputedManager.ts
  46. 58
      io.sc.platform.core.frontend/src/platform/components/grid/ts/constant/Constant.ts
  47. 85
      io.sc.platform.core.frontend/src/platform/components/grid/ts/constant/src/CriteriaOperator.ts
  48. 24
      io.sc.platform.core.frontend/src/platform/components/grid/ts/constant/src/DndMode.ts
  49. 34
      io.sc.platform.core.frontend/src/platform/components/grid/ts/constant/src/EditStatus.ts
  50. 63
      io.sc.platform.core.frontend/src/platform/components/grid/ts/constant/src/FieldNames.ts
  51. 39
      io.sc.platform.core.frontend/src/platform/components/grid/ts/constant/src/FormStatus.ts
  52. 24
      io.sc.platform.core.frontend/src/platform/components/grid/ts/constant/src/GroupMode.ts
  53. 29
      io.sc.platform.core.frontend/src/platform/components/grid/ts/constant/src/GroupStartOpen.ts
  54. 34
      io.sc.platform.core.frontend/src/platform/components/grid/ts/constant/src/SelectMode.ts
  55. 175
      io.sc.platform.core.frontend/src/platform/components/grid/ts/event/EventManager.ts
  56. 18
      io.sc.platform.core.frontend/src/platform/components/grid/ts/event/src/AfterDragAndDrop.ts
  57. 18
      io.sc.platform.core.frontend/src/platform/components/grid/ts/event/src/AfterEditorDataSubmit.ts
  58. 18
      io.sc.platform.core.frontend/src/platform/components/grid/ts/event/src/AfterEditorOpen.ts
  59. 18
      io.sc.platform.core.frontend/src/platform/components/grid/ts/event/src/AfterRemove.ts
  60. 18
      io.sc.platform.core.frontend/src/platform/components/grid/ts/event/src/AfterRequestData.ts
  61. 35
      io.sc.platform.core.frontend/src/platform/components/grid/ts/event/src/BeforeEditorDataSubmit.ts
  62. 33
      io.sc.platform.core.frontend/src/platform/components/grid/ts/event/src/BeforeRemove.ts
  63. 25
      io.sc.platform.core.frontend/src/platform/components/grid/ts/event/src/BeforeRequestData.ts
  64. 61
      io.sc.platform.core.frontend/src/platform/components/grid/ts/event/src/RowClick.ts
  65. 27
      io.sc.platform.core.frontend/src/platform/components/grid/ts/event/src/RowDbClick.ts
  66. 33
      io.sc.platform.core.frontend/src/platform/components/grid/ts/event/src/UpdateTicked.ts
  67. 38
      io.sc.platform.core.frontend/src/platform/components/grid/ts/event/src/UpdateTickeds.ts
  68. 134
      io.sc.platform.core.frontend/src/platform/components/grid/ts/expose-api/ExposeApiManager.ts
  69. 133
      io.sc.platform.core.frontend/src/platform/components/grid/ts/expose-api/src/Button.ts
  70. 40
      io.sc.platform.core.frontend/src/platform/components/grid/ts/expose-api/src/ComponentRef.ts
  71. 182
      io.sc.platform.core.frontend/src/platform/components/grid/ts/expose-api/src/GetData.ts
  72. 209
      io.sc.platform.core.frontend/src/platform/components/grid/ts/expose-api/src/LocalMode.ts
  73. 183
      io.sc.platform.core.frontend/src/platform/components/grid/ts/expose-api/src/Operator.ts
  74. 118
      io.sc.platform.core.frontend/src/platform/components/grid/ts/expose-api/src/ResetProperty.ts
  75. 158
      io.sc.platform.core.frontend/src/platform/components/grid/ts/function/Criteria.ts
  76. 411
      io.sc.platform.core.frontend/src/platform/components/grid/ts/function/DragAndDrop.ts
  77. 112
      io.sc.platform.core.frontend/src/platform/components/grid/ts/function/InlineEdit.ts
  78. 272
      io.sc.platform.core.frontend/src/platform/components/grid/ts/function/Operator.ts
  79. 339
      io.sc.platform.core.frontend/src/platform/components/grid/ts/function/RequestApi.ts
  80. 440
      io.sc.platform.core.frontend/src/platform/components/grid/ts/function/RowData.ts
  81. 105
      io.sc.platform.core.frontend/src/platform/components/grid/ts/grid.ts
  82. 14
      io.sc.platform.core.frontend/src/platform/components/grid/ts/index.ts
  83. 55
      io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/Button.ts
  84. 227
      io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/ButtonManager.ts
  85. 30
      io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/buttons/Add.ts
  86. 36
      io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/buttons/AddChild.ts
  87. 30
      io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/buttons/AddTop.ts
  88. 34
      io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/buttons/AdvancedQuery.ts
  89. 45
      io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/buttons/CellEdit.ts
  90. 41
      io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/buttons/Clone.ts
  91. 40
      io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/buttons/Edit.ts
  92. 42
      io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/buttons/Expand.ts
  93. 89
      io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/buttons/Export.ts
  94. 30
      io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/buttons/FullScreen.ts
  95. 44
      io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/buttons/InlineCellEdit.ts
  96. 37
      io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/buttons/InlineRowEdit.ts
  97. 35
      io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/buttons/InlineRowsEdit.ts
  98. 35
      io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/buttons/MoreQuery.ts
  99. 27
      io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/buttons/Query.ts
  100. 27
      io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/buttons/Refresh.ts

3
io.sc.platform.core.frontend/package.json

@ -144,6 +144,7 @@
"vue-i18n": "10.0.3",
"vue-router": "4.4.5",
"xml-formatter": "3.6.3",
"node-sql-parser": "5.3.2"
"node-sql-parser": "5.3.2",
"sort-array": "5.0.0"
}
}

5
io.sc.platform.core.frontend/src/platform/components/form/WForm.vue

@ -33,7 +33,7 @@
<script setup lang="ts">
import { ref, reactive, watch, computed, toRaw, useAttrs, getCurrentInstance } from 'vue';
import { useQuasar } from 'quasar';
import { VueTools, Tools } from '@/platform';
import { VueTools, Tools, $t } from '@/platform';
import { PageStatusEnum } from '@/platform/components/utils';
import { getDefaultValue } from './FormField.ts';
@ -318,6 +318,9 @@ const setValidationErrors = (errors: errorType[]) => {
formFields[name].error = true;
formFields[name].errorMessage = grouped[name]
.map((obj) => {
if (!Tools.isEmpty(obj['errorMessageI18nKey'])) {
return $t(obj['errorMessageI18nKey']);
}
return obj.errorMessage;
})
.join('、');

32
io.sc.platform.core.frontend/src/platform/components/grid/Body.vue

@ -0,0 +1,32 @@
<template>
<!-- 单独分组行 -->
<GroupTr v-if="useGroupTrComputed" :scope="props.scope"></GroupTr>
<!-- 普通行 -->
<Tr v-else :scope="props.scope"></Tr>
<!-- 追加行 -->
<GridAppendRow :scope="props.scope"></GridAppendRow>
</template>
<script setup lang="ts">
import { inject, computed } from 'vue';
import { Constant, GridTools } from './ts/index';
import Tr from './Tr.vue';
import GroupTr from './extra/group/GroupTr.vue';
import GridAppendRow from './extra/append/AppendRow.vue';
const tools = <GridTools>inject('tools');
const props = defineProps({
scope: {
//
type: Object,
default: () => {
return {};
},
},
});
const useGroupTrComputed = computed(() => {
return tools.props.groupMode === Constant.GROUP_MODE.ALONE && !tools.props.tree && tools.table.configStore.aloneGroupByField;
});
</script>

134
io.sc.platform.core.frontend/src/platform/components/grid/CellEditor.vue

@ -1,134 +0,0 @@
<template>
<w-dialog ref="dialogRef" :title="dialog.title" v-bind="props.grid.props.editor?.cellEditor" :buttons="dialog.buttons">
<w-form ref="dialogFormRef" :cols-num="1" :fields="fieldsComputed" class="pt-1.5 px-1.5"></w-form>
</w-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, inject, computed, toRaw } from 'vue';
import { t, Tools, noErrorAxios, NotifyManager, ServerExceptionHandler } from '@/platform';
const dialogRef = ref();
const dialogFormRef = ref();
const props = defineProps({
grid: {
//
type: Object,
default: () => {
return {};
},
},
url: {
// url
type: Object,
default: () => {
return {};
},
},
getRow: {
type: Function,
default: () => {},
},
componentInfo: {
type: Object,
default: () => {
return {};
},
},
rowKeyName: {
type: String,
default: '',
},
});
const table = inject('table');
const dialog = {
title: t('action.edit'),
buttons: [
{
icon: 'beenhere',
labelI18nKey: props.grid.props.localMode ? 'confirm' : 'action.submit',
label: t(props.grid.props.localMode ? 'confirm' : 'action.submit'),
loading: false,
click: async () => {
dialog.buttons[0].loading = true;
const result = await dialogFormRef.value.validate();
if (result) {
const cellSelected = table['cellSelected'];
const data = dialogFormRef.value.getData();
const row = props.getRow(table.rows, cellSelected['row'][props.rowKeyName], false);
if (row) {
row[cellSelected['colName']] = data[cellSelected['colName']];
//
if (!props.grid.props.localMode) {
const requestParams = {
method: 'PUT',
headers: { 'content-type': 'application/json;charset=utf-8;' },
data: toRaw(row),
url: getUrl(row[props.grid.props.primaryKey]),
};
noErrorAxios(requestParams)
.then((resp) => {
dialog.buttons[0].loading = false;
props.grid.emit('afterEditorDataSubmit', { grid: props.grid, data: resp.data });
NotifyManager.info(t('tip.operationSuccess'));
dialogRef.value.hide();
})
.catch((error) => {
if (error?.code === 1001) {
//
dialogFormRef.value.setValidationErrors(error.data);
} else {
ServerExceptionHandler.handle(error);
}
dialog.buttons[0].loading = false;
});
} else {
dialogRef.value.hide();
}
}
}
dialog.buttons[0].loading = false;
},
},
],
};
const fieldsComputed = computed(() => {
const fields = <any>[];
const cellSelected = table['cellSelected'];
if (Object.keys(cellSelected).length > 0) {
const column = table['columns'].find((item) => item['name'] === cellSelected['colName']);
if (column) {
fields.push({
name: column['name'],
type: column['type'],
label: column['label'],
defaultValue: cellSelected['value'],
...column['attrs'],
});
}
}
return fields;
});
const getUrl = (primaryKey) => {
if (!Tools.isEmpty(props.url.editDataUrl)) {
return props.url.editDataUrl + '/' + primaryKey;
} else {
return props.url.dataUrl + '/' + primaryKey;
}
};
const getDialog = () => {
return dialogRef.value;
};
const getForm = () => {
return dialogFormRef.value;
};
defineExpose({
getDialog,
getForm,
});
</script>

27
io.sc.platform.core.frontend/src/platform/components/grid/GridAppendRow.vue

@ -1,27 +0,0 @@
<template>
<q-tr v-for="(appendRow, rowIndex) in props.grid.props.appendRows" :key="rowIndex" :no-hover="true" :class="''">
<q-td v-for="(col, colIndex) in appendRow" :key="colIndex" :rowspan="col['rowSpan'] ? col['rowSpan'] : 1" :colspan="col['colSpan'] ? col['colSpan'] : 1">
<template v-if="typeof col['value'] === 'function'">
<GridAppendContent :value="col.value({ grid: props.grid, rows: props.grid.getRows() })"></GridAppendContent>
</template>
<template v-else>
<span v-dompurify-html="!Tools.isEmpty(col['value']) ? col['value'] : ''"></span>
</template>
</q-td>
</q-tr>
</template>
<script setup lang="ts">
import { Tools } from '@/platform';
import GridAppendContent from './GridAppendContent.vue';
const props = defineProps({
grid: {
//
type: Object,
default: () => {
return {};
},
},
});
</script>

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

@ -1,376 +0,0 @@
<template>
<template v-if="props.grid.props.tree">
<TreeGridRow
:ref="(el) => setTreeRowComponentRef(el, scope.row)"
:grid="props.grid"
:columns-map="props.tableColumnsMap"
:row="props.scope.row"
:cols="props.scope.cols"
:row-key="props.rowKeyName"
:grid-row-click="props.rowClick"
:grid-row-db-click="props.rowDbClick"
:after-drag-and-drop="props.afterDragAndDrop"
:get-row="props.getRow"
:url="props.url"
:get-row-component-refs="getRowComponentRefs"
:set-old-value="setOldValue"
:no-data-tr-colspan="noDataTrColspan"
:updates="updates"
:row-index="scope.rowIndex"
></TreeGridRow>
</template>
<template v-else>
<q-tr
ref="trRef"
:no-hover="props.grid.props.selectMode === selectMode.row ? false : true"
:class="props.scope.row[table.selectedField] && props.grid.props.selectMode === selectMode.row ? 'selected' : ''"
:props="props.scope"
:draggable="draggableComputed"
@click.stop="props.rowClick($event, scope.row, scope.rowIndex)"
@dblclick.stop="props.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 && props.grid.props.selectMode !== selectMode.none" class="text-center" style="padding: 0; width: 50px">
<q-checkbox
v-model="props.getRow(table.rows, scope.row[props.rowKeyName], false)[table.tickedField]"
flat
:dense="props.denseBody"
@update:model-value="updateTicked($event, scope.row)"
/>
</q-td>
<template v-for="(col, index) in scope.cols" :key="index">
<GridTd
:ref="(el) => setComponentRef(el, scope.row, col)"
:grid="props.grid"
:get-row="props.getRow"
:row-key-name="props.rowKeyName"
:scope="scope"
:col="col"
:value="col.value"
:is-selected-row="isSelectedRowComputed"
></GridTd>
</template>
</q-tr>
<GridEditToolbar
:grid="props.grid"
:url="props.url"
:row="props.scope.row"
:row-key-name="props.rowKeyName"
:get-row="props.getRow"
:get-row-component-refs="getRowComponentRefs"
:set-old-value="setOldValue"
:no-data-tr-colspan="noDataTrColspan"
></GridEditToolbar>
<GridAppendRow v-if="showAppendRowsComputed" :grid="props.grid"></GridAppendRow>
</template>
</template>
<script setup lang="ts">
import { computed, inject, ref, toRaw } from 'vue';
import { Tools, noErrorAxios, NotifyManager, t, ServerExceptionHandler } from '@/platform';
import { dndMode, dndImage, selectMode, editStatus } from './ts/grid';
import TreeGridRow from './TreeGridRow.vue';
import GridTd from './GridTd.vue';
import GridEditToolbar from './GridEditToolbar.vue';
import GridAppendRow from './GridAppendRow.vue';
const trRef = ref();
const props = defineProps({
grid: {
//
type: Object,
default: () => {
return {};
},
},
url: {
// url
type: Object,
default: () => {
return {};
},
},
scope: {
//
type: Object,
default: () => {
return {};
},
},
tableColumnsMap: {
type: Map,
default: () => {
return new Map();
},
},
rowKeyName: {
type: String,
default: '',
},
rowClick: {
type: Function,
default: () => {},
},
rowDbClick: {
type: Function,
default: () => {},
},
afterDragAndDrop: {
type: Function,
default: () => {},
},
denseBody: {
type: Boolean,
default: () => {
return false;
},
},
getRow: {
type: Function,
default: () => {},
},
setOldValue: {
type: Function,
default: () => {},
},
noDataTrColspan: {
type: Number,
default: () => {
return 0;
},
},
allTickedStatus: {
type: Function,
default: () => {},
},
});
const table = inject('table');
const componentRef = ref({});
const getRowComponentRefs = (rowKey: string | Array<string>) => {
const refs = <any>[];
if (!Tools.isEmpty(componentRef.value)) {
const filterResult = Object.keys(componentRef.value).filter((item) => {
if (typeof rowKey === 'string') {
return item.startsWith(rowKey + '_');
} else {
return true;
}
});
if (filterResult.length > 0) {
filterResult.forEach((key) => {
refs.push(componentRef.value[key]);
});
}
}
return refs;
};
const setComponentRef = (el, row, col) => {
if (el && !Tools.isEmpty(col.type)) {
componentRef.value[row[props.rowKeyName] + '_' + col.name] = el;
}
};
const setTreeRowComponentRef = (el, row) => {
if (el && !Tools.isEmpty(row)) {
componentRef.value[row[props.rowKeyName] + '_'] = el;
}
};
const checkLastRow = (row) => {
if (props.grid.props.tree && row['expand'] && !Tools.isEmpty(row.children) && row.children.length > 0) {
const childrenLastRow = row.children[row.children.length - 1];
if (childrenLastRow['expand'] && !Tools.isEmpty(childrenLastRow.children) && childrenLastRow.children.length > 0) {
return checkLastRow(childrenLastRow);
} else {
return childrenLastRow[props.rowKeyName] === props.scope.row[props.rowKeyName];
}
} else {
return row[props.rowKeyName] === props.scope.row[props.rowKeyName];
}
};
const showAppendRowsComputed = computed(() => {
if (Array.isArray(props.grid.props.appendRows) && props.grid.props.appendRows.length > 0) {
const lastRow = table.rows[table.rows.length - 1];
return checkLastRow(lastRow);
}
return false;
});
const draggableComputed = computed(() => {
if (
props.grid.props.dndMode &&
typeof props.grid.props.dndMode === 'string' &&
!Tools.isEmpty(dndMode[props.grid.props.dndMode]) &&
table.bodyEditStatus === editStatus.none
) {
return true;
}
return false;
});
const isSelectedRowComputed = computed(() => {
const selected = props.grid.getSelectedRow();
if (!Tools.isEmpty(selected)) {
return props.scope.row[props.rowKeyName] === props.grid.getSelectedRow()[props.rowKeyName];
}
return false;
});
//
const gridTrMiddleHeightComputed = computed(() => {
if (trRef?.value) {
return trRef.value.$el.offsetHeight / 2;
} else {
return (table.dense || table.denseBody ? 24 : 48) / 2;
}
});
//
const updates = (data, callback) => {
const requestParams = {
method: 'PUT',
headers: { 'content-type': 'application/json;charset=utf-8;' },
data: data,
url: props.url.dataUrl + '/updates',
};
noErrorAxios(requestParams)
.then((resp) => {
if (!Tools.isEmpty(callback)) {
callback(resp?.data);
}
})
.catch((error) => {
if (error.code === 1001) {
NotifyManager.error('服务器验证未通过');
} else {
ServerExceptionHandler.handle(error);
}
});
};
//
const onDragStart = (e, scope) => {
const img = new Image();
img.src = dndImage;
e.dataTransfer.setDragImage(img, 0, 0);
const currPageIndex = table.rows.findIndex((item) => {
return item[props.rowKeyName] === scope.row[props.rowKeyName];
});
const currPageStartIndex = scope.rowIndex - currPageIndex;
if (props.grid.props.pageable) {
table.dragRow = { row: { ...scope.row }, rowIndex: scope.rowIndex, currPageIndex: currPageIndex, currPageStartIndex };
} else {
table.dragRow = { row: { ...scope.row }, rowIndex: scope.rowIndex, currPageIndex: scope.rowIndex, currPageStartIndex };
}
e.dataTransfer.dropEffect = 'move';
};
const addDragTopStyle = (e) => {
if (e.target?.parentNode?.children) {
for (let i = 0; i < e.target.parentNode.children.length; i++) {
e.target.parentNode.children[i].style.borderTopWidth = '2px';
e.target.parentNode.children[i].style.borderTopStyle = 'dashed';
e.target.parentNode.children[i].style.borderTopColor = 'orange';
}
}
};
const removeDragTopStyle = (e) => {
if (e.target?.parentNode?.children) {
for (let i = 0; i < e.target.parentNode.children.length; i++) {
e.target.parentNode.children[i].style.borderTopWidth = '';
e.target.parentNode.children[i].style.borderTopStyle = '';
e.target.parentNode.children[i].style.borderTopColor = '';
}
}
};
const addDragBottomStyle = (e) => {
if (e.target?.parentNode?.children) {
for (let i = 0; i < e.target.parentNode.children.length; i++) {
e.target.parentNode.children[i].style.borderBottomWidth = '2px';
e.target.parentNode.children[i].style.borderBottomStyle = 'dashed';
e.target.parentNode.children[i].style.borderBottomColor = 'orange';
}
}
};
const removeDragBottomStyle = (e) => {
if (e.target?.parentNode?.children) {
for (let i = 0; i < e.target.parentNode.children.length; i++) {
e.target.parentNode.children[i].style.borderBottomWidth = '';
e.target.parentNode.children[i].style.borderBottomStyle = '';
e.target.parentNode.children[i].style.borderBottomColor = '';
}
}
};
//
const onDragLeave = (e) => {
removeDragTopStyle(e);
removeDragBottomStyle(e);
};
//
const onDragOver = (e, scope) => {
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);
}
};
//
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) {
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);
}
const updateData = <any>[];
table.rows.forEach((item, index) => {
if (!Tools.isEmpty(item)) {
item[props.grid.props.dndOrderBy] = currPageStartIndex + index + 1;
updateData.push(toRaw(item));
}
});
if (props.grid.props.dndMode === dndMode.server && updateData.length > 0) {
// 访
updates(updateData, () => {});
}
props.afterDragAndDrop(updateData);
};
const updateTicked = (evt: Event, row: any) => {
if (table.bodyEditStatus === 'none') {
props.getRow(table.rows, row[props.rowKeyName], false)[table.selectedField] = row[table.tickedField];
props.allTickedStatus();
if (!row[table.tickedField]) {
//
table.cellSelected = {};
}
if (props.grid.props.onUpdateTicked) {
props.grid.emit('updateTicked', { grid: props.grid, evt, row });
}
} else {
props.getRow(table.rows, row[props.rowKeyName], false)[table.tickedField] = !props.getRow(table.rows, row[props.rowKeyName], false)[table.tickedField];
}
};
</script>
<style lang="css"></style>

363
io.sc.platform.core.frontend/src/platform/components/grid/GridConfig.vue

@ -1,363 +0,0 @@
<template>
<q-list padding style="min-width: 250px">
<q-item :clickable="false">
<q-item-section>
<q-item-label>全屏</q-item-label>
</q-item-section>
<q-item-section side>
<q-btn-group outline flat dense unelevated spread>
<q-btn
:color="!scope.inFullscreen ? 'primary' : ''"
:outline="scope.inFullscreen ? true : false"
dense
label="正常"
unelevated
@click="
() => {
if (scope.inFullscreen) {
scope.toggleFullscreen();
table.gridConfig = false;
}
}
"
/>
<q-btn
:color="scope.inFullscreen ? 'primary' : ''"
:outline="!scope.inFullscreen ? true : false"
dense
label="全屏"
unelevated
@click="
() => {
if (!scope.inFullscreen) {
scope.toggleFullscreen();
table.gridConfig = false;
}
}
"
/>
</q-btn-group>
</q-item-section>
</q-item>
<q-separator />
<q-item :clickable="false">
<q-item-section>
<q-item-label>复选框</q-item-label>
</q-item-section>
<q-item-section side>
<q-btn-group outline flat dense unelevated spread>
<q-btn
:color="table.checkboxSelection ? 'primary' : ''"
:outline="table.checkboxSelection ? false : true"
dense
label="显示"
unelevated
@click="
() => {
table.checkboxSelection = true;
grid.refreshStyle(100);
}
"
/>
<q-btn
:color="table.checkboxSelection ? '' : 'primary'"
:outline="table.checkboxSelection ? true : false"
dense
label="隐藏"
unelevated
@click="
() => {
table.checkboxSelection = false;
grid.refreshStyle(100);
}
"
/>
</q-btn-group>
</q-item-section>
</q-item>
<template v-if="!grid.props.tree">
<q-separator />
<q-item :clickable="false">
<q-item-section>
<q-item-label>序号</q-item-label>
</q-item-section>
<q-item-section side>
<q-btn-group outline flat dense unelevated spread>
<q-btn
:color="table.sortNo ? 'primary' : ''"
:outline="table.sortNo ? false : true"
dense
label="显示"
unelevated
@click="
() => {
table.columns[0].showIf = true;
table.sortNo = true;
grid.refreshStyle(100);
}
"
/>
<q-btn
:color="table.sortNo ? '' : 'primary'"
:outline="table.sortNo ? true : false"
dense
label="隐藏"
unelevated
@click="
() => {
table.columns[0].showIf = false;
table.sortNo = false;
grid.refreshStyle(100);
}
"
/>
</q-btn-group>
</q-item-section>
</q-item>
</template>
<q-separator />
<q-expansion-item label="分割线">
<q-card>
<q-card-section>
<q-item v-ripple tag="label" dense>
<q-item-section side>
<q-checkbox v-model="separatorHorizontal" dense @update:model-value="separatorChange('horizontal', separatorHorizontal)" />
</q-item-section>
<q-item-section>
<q-item-label>水平</q-item-label>
</q-item-section>
</q-item>
<q-item v-ripple tag="label" dense>
<q-item-section side>
<q-checkbox v-model="separatorVertical" dense @update:model-value="separatorChange('vertical', separatorVertical)" />
</q-item-section>
<q-item-section>
<q-item-label>垂直</q-item-label>
</q-item-section>
</q-item>
<q-item v-ripple tag="label" dense>
<q-item-section side>
<q-checkbox v-model="separatorCell" dense @update:model-value="separatorChange('cell', separatorCell)" />
</q-item-section>
<q-item-section>
<q-item-label>单元格</q-item-label>
</q-item-section>
</q-item>
<q-item v-ripple tag="label" dense>
<q-item-section side>
<q-checkbox v-model="separatorNone" dense @update:model-value="separatorChange('none', separatorNone)" />
</q-item-section>
<q-item-section>
<q-item-label></q-item-label>
</q-item-section>
</q-item>
</q-card-section>
</q-card>
</q-expansion-item>
<q-separator />
<q-expansion-item label="紧凑模式">
<q-card>
<q-card-section>
<q-item v-ripple tag="label" dense>
<q-item-section side>
<q-checkbox v-model="table.dense" dense @update:model-value="denseChange()" />
</q-item-section>
<q-item-section>
<q-item-label>紧凑</q-item-label>
</q-item-section>
</q-item>
<q-item v-ripple tag="label" dense>
<q-item-section side>
<q-checkbox v-model="table.denseToolbar" dense @update:model-value="denseChange" />
</q-item-section>
<q-item-section>
<q-item-label>按钮栏紧凑</q-item-label>
</q-item-section>
</q-item>
<q-item v-ripple tag="label" dense>
<q-item-section side>
<q-checkbox v-model="table.denseHeader" dense @update:model-value="denseChange" />
</q-item-section>
<q-item-section>
<q-item-label>列头紧凑</q-item-label>
</q-item-section>
</q-item>
<q-item v-ripple tag="label" dense>
<q-item-section side>
<q-checkbox v-model="table.denseBody" dense @update:model-value="denseChange" />
</q-item-section>
<q-item-section>
<q-item-label>内容紧凑</q-item-label>
</q-item-section>
</q-item>
<q-item v-ripple tag="label" dense>
<q-item-section side>
<q-checkbox v-model="table.denseBottom" dense @update:model-value="denseChange" />
</q-item-section>
<q-item-section>
<q-item-label>分页栏紧凑</q-item-label>
</q-item-section>
</q-item>
</q-card-section>
</q-card>
</q-expansion-item>
<q-separator />
<q-item :clickable="false">
<q-item-section>
<q-item-label class="nowrap text-nowrap">固定列</q-item-label>
</q-item-section>
<q-item-section side>
<q-select
v-model="table.stickyNum"
emit-value
map-options
:hide-bottom-space="true"
:hide-hint="true"
:outlined="true"
:dense="true"
:options="stickyOptions"
@update:model-value="
() => {
grid.refreshStyle(500);
}
"
>
</q-select>
</q-item-section>
</q-item>
<q-separator />
<q-item-label header>显示列</q-item-label>
<template v-for="col in 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="
() => {
grid.refreshStyle(100);
}
"
/>
</q-item-section>
<q-item-section>
<q-item-label>{{ col.label || col.name }}</q-item-label>
</q-item-section>
</q-item>
</template>
</q-list>
</template>
<script setup lang="ts">
import { inject, ref, watch } from 'vue';
const props = defineProps({
scope: {
type: Object,
default: () => {
return {};
},
},
moreColumnTitleArray: {
//
type: Array,
default: () => {
return [];
},
},
grid: {
type: Object,
default: () => {
return {};
},
},
});
const table = inject('table');
const stickyOptions = [
{ label: '不固定', value: 0 },
{ label: '固定1列', value: 1 },
{ label: '固定2列', value: 2 },
{ label: '固定3列', value: 3 },
{ label: '固定4列', value: 4 },
{ label: '固定5列', value: 5 },
{ label: '固定6列', value: 6 },
{ label: '固定7列', value: 7 },
{ label: '固定8列', value: 8 },
{ label: '固定9列', value: 9 },
{ label: '固定10列', value: 10 },
];
const separatorHorizontal = ref(false);
const separatorVertical = ref(false);
const separatorCell = ref(false);
const separatorNone = ref(false);
const setSeparatorValue = (value) => {
if (value === 'horizontal') {
separatorHorizontal.value = true;
separatorVertical.value = false;
separatorCell.value = false;
separatorNone.value = false;
} else if (value === 'vertical') {
separatorHorizontal.value = false;
separatorVertical.value = true;
separatorCell.value = false;
separatorNone.value = false;
} else if (value === 'cell') {
separatorHorizontal.value = false;
separatorVertical.value = false;
separatorCell.value = true;
separatorNone.value = false;
} else if (value === 'none') {
separatorHorizontal.value = false;
separatorVertical.value = false;
separatorCell.value = false;
separatorNone.value = true;
}
};
if (table.separator) {
setSeparatorValue(table.separator);
}
const separatorChange = (separator, value) => {
if (!value) {
setSeparatorValue(table.separator);
} else {
table.separator = separator;
}
};
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 {
return true;
}
};
watch(
() => table.separator,
(newVal, oldVal) => {
if (newVal) {
setSeparatorValue(newVal);
}
},
);
const denseChange = () => {
props.grid.refreshStyle(0);
};
</script>

359
io.sc.platform.core.frontend/src/platform/components/grid/GridEditToolbar.vue

@ -1,359 +0,0 @@
<template>
<q-tr v-if="showRowEditButtonComputed">
<q-td :colspan="props.noDataTrColspan">
<div class="editButton text-center">
<w-toolbar
:dense="true"
align="center"
:grid="props.grid"
:buttons="[
{
label: '保存',
name: 'save',
icon: 'save',
color: 'primary',
outline: false,
click: async (args) => {
save(args);
},
},
{
label: '取消',
name: 'cancel',
icon: 'close',
color: 'blue-grey',
outline: false,
click: (args) => {
if (table.bodyEditStatus === 'rowEdit' || table.bodyEditStatus === 'cellEdit') {
props.setOldValue(args.selected);
} else if (table.bodyEditStatus === 'rowsEdit') {
props.grid.getRows().forEach((item) => {
props.setOldValue(item);
});
}
table.bodyEditStatus = 'none';
},
},
]"
></w-toolbar>
</div>
</q-td>
</q-tr>
</template>
<script setup lang="ts">
import { computed, inject, ref, toRaw } from 'vue';
import { Tools, NotifyManager, noErrorAxios, t, ServerExceptionHandler } from '@/platform';
import { editStatus } from './ts/grid';
const props = defineProps({
grid: {
//
type: Object,
default: () => {
return {};
},
},
url: {
// url
type: Object,
default: () => {
return {};
},
},
rowKeyName: {
type: String,
default: '',
},
row: {
type: Object,
default: () => {
return {};
},
},
getRow: {
type: Function,
default: () => {},
},
getRowComponentRefs: {
type: Function,
default: () => {},
},
setOldValue: {
type: Function,
default: () => {},
},
noDataTrColspan: {
type: Number,
default: () => {
return 0;
},
},
});
const table = inject('table');
const validate = async (refs) => {
let result = true;
for (let i = 0; i < refs.length; i++) {
const component = refs[i].getComponentRef();
if (component) {
if (!Tools.isEmpty(refs[i].getComponentOneRef)) {
const componentOne = refs[i].getComponentOneRef();
if (!Tools.isEmpty(componentOne) && !Tools.isEmpty(componentOne.validate)) {
const componentOneValidateResult = await componentOne.validate();
if (!componentOneValidateResult) {
result = false;
break;
}
}
}
if (props.grid.props.tree && !Tools.isEmpty(component)) {
const keys = Object.keys(component);
for (let k = 0; k < keys.length; k++) {
if (!Tools.isEmpty(component[keys[k]]?.validate)) {
const treeComponentValidateResult = await component[keys[k]].validate();
if (!treeComponentValidateResult) {
result = false;
break;
}
}
}
} else if (!Tools.isEmpty(component.validate)) {
const componentValidateResult = await component.validate();
if (!componentValidateResult) {
result = false;
break;
}
}
}
}
return result;
};
const rowSave = (args) => {
let data = args.selected['_rowOldValue'];
let submitFlag = true;
let localeUpdateFlag = false;
let url = '';
//
props.grid.emit('beforeEditorDataSubmit', {
grid: props.grid,
data: data,
callback: (handlerRequestParams: any | boolean, localeUpdate: boolean = false) => {
if (typeof handlerRequestParams === 'boolean' && handlerRequestParams === false) {
submitFlag = false;
} else {
data = handlerRequestParams;
}
localeUpdateFlag = localeUpdate;
},
});
if (localeUpdateFlag && submitFlag) {
// 访
props.grid.updateLocalData(data);
table.bodyEditStatus = 'none';
} else {
if (submitFlag) {
data = { ...args.selected, ...data };
if (!Tools.isEmpty(props.url.editDataUrl)) {
url = props.url.editDataUrl + '/' + args.selected[props.grid.props.primaryKey];
} else {
url = props.url.dataUrl + '/' + args.selected[props.grid.props.primaryKey];
}
const requestParams = {
method: 'PUT',
headers: { 'content-type': 'application/json;charset=utf-8;' },
data: data,
url: url,
};
noErrorAxios(requestParams)
.then((resp) => {
props.grid.emit('afterEditorDataSubmit', { grid: props.grid, data: resp.data });
NotifyManager.info(t('tip.operationSuccess'));
if (props.grid.props.refreshData || !props.grid.props.tree) {
props.grid.refresh();
} else if (resp.data) {
props.grid.updateLocalData(data);
}
// 退
table.bodyEditStatus = 'none';
})
.catch((error) => {
if (error.code === 1001) {
NotifyManager.error('服务器验证未通过');
} else {
ServerExceptionHandler.handle(error);
}
});
}
}
};
//
const checkDataModified = (row) => {
const keys = Object.keys(row['_rowOldValue']);
return keys.some((key) => row[key] !== row['_rowOldValue'][key]);
};
const treeDataPush = (arr) => {
const data = <any>[];
if (arr && arr.length > 0) {
arr.forEach((item) => {
if (checkDataModified(item)) {
data.push(item['_rowOldValue']);
}
const childrenData = treeDataPush(item.children);
if (childrenData.length > 0) {
data.push(...childrenData);
}
});
}
return data;
};
const rowsSave = (args) => {
let data = <any>[];
const rows = args.grid.getRows();
const isTree = props.grid.props.tree;
rows.forEach((item) => {
if (checkDataModified(item)) {
data.push(item['_rowOldValue']);
}
if (isTree && item.children) {
const childrenData = treeDataPush(item.children);
if (childrenData.length > 0) {
data.push(...childrenData);
}
}
});
let submitFlag = true;
let localeUpdateFlag = false;
//
props.grid.emit('beforeEditorDataSubmit', {
grid: props.grid,
data: data,
callback: (handlerRequestParams: any | boolean, localeUpdate: boolean = false) => {
if (typeof handlerRequestParams === 'boolean' && handlerRequestParams === false) {
submitFlag = false;
} else {
data = handlerRequestParams;
}
localeUpdateFlag = localeUpdate;
},
});
if ((localeUpdateFlag && submitFlag) || props.grid.props.localMode) {
// 访
data.forEach((item) => {
props.grid.updateLocalData(item);
});
table.bodyEditStatus = 'none';
} else {
if (submitFlag) {
updates(data, (callbackData) => {
NotifyManager.info(t('tip.operationSuccess'));
if (props.grid.props.refreshData || !props.grid.props.tree) {
props.grid.refresh();
} else if (!Tools.isEmpty(callbackData)) {
callbackData.forEach((item) => {
props.grid.updateLocalData(item);
});
}
// 退
table.bodyEditStatus = 'none';
});
}
}
};
const save = async (args) => {
const refs = props.getRowComponentRefs(table.bodyEditStatus === 'rowEdit' || table.bodyEditStatus === 'cellEdit' ? args.selected[props.rowKeyName] : []);
const result = await validate(refs);
if (!result) {
NotifyManager.error('验证未通过');
} else {
if (table.bodyEditStatus === 'rowEdit' || table.bodyEditStatus === 'cellEdit') {
rowSave(args);
} else if (table.bodyEditStatus === 'rowsEdit') {
rowsSave(args);
}
}
};
//
const updates = (data, callback) => {
const requestParams = {
method: 'PUT',
headers: { 'content-type': 'application/json;charset=utf-8;' },
data: data,
url: props.url.dataUrl + '/updates',
};
noErrorAxios(requestParams)
.then((resp) => {
if (!Tools.isEmpty(callback)) {
callback(resp?.data);
}
})
.catch((error) => {
if (error.code === 1001) {
NotifyManager.error('服务器验证未通过');
} else {
ServerExceptionHandler.handle(error);
}
});
};
const showRowEditButtonComputed = computed(() => {
if (props.grid.props.localMode && table.bodyEditStatus !== editStatus.rows) {
return false;
}
if (table.bodyEditStatus === 'cellEdit') {
const selected = props.grid.getSelectedCell();
if (selected?.colName && selected.colName === table.cellSelected['colName'] && selected.row[props.rowKeyName] === props.row[props.rowKeyName]) {
return true;
}
return false;
} else if (table.bodyEditStatus === 'rowEdit' && isSelectedRowComputed.value) {
return true;
} else if (table.bodyEditStatus === 'rowsEdit' && table.rows.length > 0 && isLastRowComputed.value) {
return true;
} else {
return false;
}
});
const checkLastRow = (row) => {
if (props.grid.props.tree && row['expand'] && !Tools.isEmpty(row.children) && row.children.length > 0) {
const childrenLastRow = row.children[row.children.length - 1];
if (childrenLastRow['expand'] && !Tools.isEmpty(childrenLastRow.children) && childrenLastRow.children.length > 0) {
return checkLastRow(childrenLastRow);
} else {
return childrenLastRow[props.rowKeyName] === props.row[props.rowKeyName];
}
} else {
return row[props.rowKeyName] === props.row[props.rowKeyName];
}
};
const isLastRowComputed = computed(() => {
const lastRow = table.rows[table.rows.length - 1];
return checkLastRow(lastRow);
});
const isSelectedRowComputed = computed(() => {
const selected = props.grid.getSelectedRow();
if (!Tools.isEmpty(selected)) {
return props.row[props.rowKeyName] === props.grid.getSelectedRow()[props.rowKeyName];
}
return false;
});
defineExpose({});
</script>
<style lang="css">
.editButton {
position: sticky;
background-color: white;
left: 45%;
width: 150px;
}
</style>

232
io.sc.platform.core.frontend/src/platform/components/grid/GridEditor.vue

@ -1,232 +0,0 @@
<template>
<w-dialog ref="dialogRef" v-bind="props.grid.props.editor.dialog" :title="dialog.dialogTitle" :buttons="dialogButtonsComputed">
<w-form ref="dialogFormRef" v-bind="props.grid.props.editor.form" class="pt-1.5 px-1.5"></w-form>
</w-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, inject, computed } from 'vue';
import { t, Tools, noErrorAxios, NotifyManager, ServerExceptionHandler } from '@/platform';
const dialogRef = ref();
const dialogFormRef = ref();
const props = defineProps({
grid: {
//
type: Object,
default: () => {
return {};
},
},
url: {
// url
type: Object,
default: () => {
return {};
},
},
request: {
type: Function,
default: () => {},
},
setRowDataExtraProperty: {
type: Function,
default: () => {},
},
getRow: {
type: Function,
default: () => {},
},
});
const table = inject('table');
const dialogButtonsComputed = computed(() => {
if (props.grid.props.editor?.dialog?.buttons) {
return [...props.grid.props.editor.dialog.buttons, ...dialog.dialogButtons];
}
return dialog.dialogButtons;
});
const save = async () => {
dialog.dialogButtons[0].loading = true;
const formStatus = dialogFormRef.value.getStatus();
const validate = await dialogFormRef.value.validate();
if (validate) {
let dialogFormData = dialogFormRef.value.getData();
const selected = props.grid.getSelectedRow();
if (formStatus === 'edit' && selected[props.grid.props.primaryKey] && Tools.isEmpty(dialogFormData[props.grid.props.primaryKey])) {
dialogFormData[props.grid.props.primaryKey] = selected[props.grid.props.primaryKey];
}
let submitFlag = true;
let closeDialog = true;
props.grid.emit('beforeEditorDataSubmit', {
grid: props.grid,
data: dialogFormData,
callback: (handlerRequestParams: any | boolean, closeFlag: boolean = true) => {
if (typeof handlerRequestParams === 'boolean' && handlerRequestParams === false) {
submitFlag = false;
} else {
dialogFormData = handlerRequestParams;
}
closeDialog = closeFlag;
},
});
if (submitFlag) {
if (formStatus === 'addTop') {
dialogFormData[props.grid.props.foreignKey] = null;
} else if (formStatus === 'addChild') {
dialogFormData[props.grid.props.foreignKey] = selected[props.grid.props.primaryKey];
} else if ((formStatus === 'edit' || formStatus === 'clone') && selected[props.grid.props.foreignKey]) {
dialogFormData[props.grid.props.foreignKey] = selected[props.grid.props.foreignKey];
}
if (formStatus === 'edit') {
//
dialogFormData = { ...selected, ...dialogFormData };
}
if (props.grid.props.localMode) {
if (formStatus === 'add' || formStatus === 'clone' || formStatus === 'addTop' || formStatus === 'addChild') {
addData(dialogFormData);
} else {
updateData(dialogFormData);
}
dialog.dialogButtons[0].loading = false;
if (closeDialog) {
dialogRef.value.hide();
}
} else {
let requestParams = {
method: getMethod(formStatus),
headers: { 'content-type': 'application/json;charset=utf-8;' },
data: dialogFormData,
url: getUrl(formStatus, dialogFormData),
};
dialog.dialogButtons[0].loading = false;
noErrorAxios(requestParams)
.then((resp) => {
dialog.dialogButtons[0].loading = false;
props.grid.emit('afterEditorDataSubmit', { grid: props.grid, data: resp.data });
NotifyManager.info(t('tip.operationSuccess'));
if (closeDialog) {
dialogRef.value.hide();
}
if (props.grid.props.refreshData || !props.grid.props.tree) {
props.grid.refresh();
} else if (resp.data && (formStatus === 'add' || formStatus === 'clone' || formStatus === 'addTop' || formStatus === 'addChild')) {
addData(resp.data);
} else if (resp.data) {
updateData(resp.data);
}
})
.catch((error) => {
if (error?.code === 1001) {
//
dialogFormRef.value.setValidationErrors(error.data);
} else {
ServerExceptionHandler.handle(error);
}
dialog.dialogButtons[0].loading = false;
});
}
} else {
dialog.dialogButtons[0].loading = false;
if (closeDialog) {
dialogRef.value.hide();
}
}
} else {
dialog.dialogButtons[0].loading = false;
}
};
const dialog = reactive({
dialogTitle: t('action.addNew'),
dialogButtons: [
{
icon: 'beenhere',
labelI18nKey: props.grid.props.localMode ? 'confirm' : 'action.submit',
label: t(props.grid.props.localMode ? 'confirm' : 'action.submit'),
loading: false,
click: () => {
save();
},
},
],
});
//
const addTreeRow = (row) => {
if (Tools.isEmpty(row[props.grid.props.foreignKey])) {
table.rows.push(row);
} else {
const parent = props.getRow(table.rows, row[props.grid.props.foreignKey], true);
if (parent) {
if (parent['children'] && Array.isArray(parent['children'])) {
parent['children'].push(row);
} else {
parent['children'] = [row];
}
}
}
};
const addData = (rowData) => {
if (props.grid.props.tree) {
addTreeRow(rowData);
props.setRowDataExtraProperty(table.rows);
} else {
props.grid.addLocalData(rowData, false);
}
};
const updateData = (rowData) => {
const selected = props.grid.getSelectedRow();
if (Tools.isEmpty(rowData[props.grid.props.primaryKey])) {
rowData[props.grid.props.primaryKey] = selected[props.grid.props.primaryKey];
}
rowData[props.grid.props.selectedField] = true;
if (selected['children']) {
rowData['children'] = selected['children'];
}
props.grid.updateLocalData(rowData);
};
const getMethod = (formStatus: string) => {
if (formStatus === 'add' || formStatus === 'clone' || formStatus === 'addTop' || formStatus === 'addChild') {
return 'POST';
} else {
return 'PUT';
}
};
const getUrl = (formStatus: string, formData: any) => {
if (formStatus === 'add' || formStatus === 'clone' || formStatus === 'addTop' || formStatus === 'addChild') {
if (!Tools.isEmpty(props.url.addDataUrl)) {
return props.url.addDataUrl;
} else {
return props.url.dataUrl;
}
} else {
if (!Tools.isEmpty(props.url.editDataUrl)) {
return props.url.editDataUrl + '/' + formData[props.grid.props.primaryKey];
} else {
return props.url.dataUrl + '/' + formData[props.grid.props.primaryKey];
}
}
};
const resetButtonLabel = () => {
dialog.dialogButtons[0].label = t(dialog.dialogButtons[0].labelI18nKey);
};
const getDialog = () => {
return dialogRef.value;
};
const getForm = () => {
return dialogFormRef.value;
};
defineExpose({
resetButtonLabel,
getDialog,
getForm,
});
</script>
<style lang="css"></style>

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

@ -1,73 +0,0 @@
<template>
<template v-if="props.grid.props.pageable && !props.grid.props.tree">
<template v-if="props.state.refHeightWidth.middleWidth > 600">
<q-pagination
v-model="page"
:boundary-links="props.state.pagination.config.boundaryLinks"
:boundary-numbers="props.state.pagination.config.boundaryNumbers"
:direction-links="props.state.pagination.config.directionLinks"
:ellipses="props.state.pagination.config.ellipses"
:max-pages="props.state.pagination.config.maxPages"
:min="1"
:max="props.scope.pagesNumber"
:size="props.denseBottom ? '10px' : ''"
@update:model-value="pageChange"
/>
</template>
<template v-else>
<q-pagination
v-model="page"
:boundary-links="props.state.pagination.config.boundaryLinks"
:boundary-numbers="props.state.pagination.config.boundaryNumbers"
:direction-links="props.state.pagination.config.directionLinks"
:ellipses="props.state.pagination.config.ellipses"
:max-pages="3"
:min="1"
:max="props.scope.pagesNumber"
:size="props.denseBottom ? '10px' : ''"
@update:model-value="pageChange"
/>
</template>
<span>{{ $t('tip.pagenation.totalRecord', { count: props.state.pagination.rowsNumber }) }}</span>
</template>
<template v-else> {{ $t('tip.pagenation.totalRecord', { count: props.state.pagination.rowsNumber }) }} </template>
</template>
<script setup lang="ts">
const page = defineModel({ type: Number, default: 1 });
const props = defineProps({
grid: {
//
type: Object,
default: () => {
return {};
},
},
scope: {
//
type: Object,
default: () => {
return {};
},
},
state: {
type: Object,
default: () => {
return {};
},
},
denseBottom: {
type: Boolean,
default: false,
},
request: {
type: Function,
default: () => {},
},
});
const pageChange = (value) => {
page.value = value;
props.request(props.state);
};
</script>
<style lang="css"></style>

210
io.sc.platform.core.frontend/src/platform/components/grid/GridTd.vue

@ -1,210 +0,0 @@
<template>
<template v-if="rowSpanIsFirstComputed">
<q-td
ref="tdRef"
:key="col.name"
:props="scope"
:title="titleComputed"
:class="props.grid.props.selectMode === selectMode.cell && (table.bodyEditStatus === 'none' || props.grid.props.localMode) ? tdClassComputed : ''"
:rowspan="rowSpanComputed"
:style="col.type && table.bodyEditStatus !== 'none' ? 'width: ' + (tdWidth - 24) + 'px;' : ''"
@click="
() => {
// 退
if (props.grid.props.localMode && table.bodyEditStatus === editStatus.cell && props.grid.props.selectMode === selectMode.cell) {
if (table['cellSelected']['colName'] !== col['name'] || table['cellSelected']['row'][props.rowKeyName] !== props.scope.row[props.rowKeyName]) {
table.bodyEditStatus = 'none';
}
} else if (props.grid.props.localMode && table.bodyEditStatus === editStatus.row && props.grid.props.selectMode === selectMode.cell) {
if (table['cellSelected']['row'][props.rowKeyName] !== props.scope.row[props.rowKeyName]) {
table.bodyEditStatus = 'none';
}
}
if (table.bodyEditStatus === 'none' && props.grid.props.selectMode === selectMode.cell) {
table['cellSelected'] = {
row: toRaw(scope.row),
rowKey: scope.row[props.rowKeyName],
primaryKey: scope.row[props.grid.props.primaryKey],
colName: col['name'],
value: scope.row[col['name']],
};
}
}
"
>
<template v-if="col.name === '_sortNo_'">
{{ scope.rowIndex + 1 }}
</template>
<template
v-if="
!Tools.isEmpty(col.type) &&
((props.isSelectedRow && table.bodyEditStatus === 'rowEdit') ||
table.bodyEditStatus === 'rowsEdit' ||
(isSelectedCellComputed && table.bodyEditStatus === 'cellEdit'))
"
>
<template v-if="props.grid.props.localMode && table.bodyEditStatus !== editStatus.rows">
<component
:is="col.type"
ref="componentRef"
v-bind="componentAttrs(col)"
v-model="props.getRow(table.rows, scope.row[props.rowKeyName], false)[col.name]"
bg-color="light-green-1"
></component>
</template>
<template v-else>
<component
:is="col.type"
ref="componentRef"
v-bind="componentAttrs(col)"
v-model="props.getRow(table.rows, scope.row[props.rowKeyName], false)['_rowOldValue'][col.name]"
bg-color="light-green-1"
></component>
</template>
</template>
<template v-else>
<template v-if="!Tools.isEmpty(value) && typeof value === 'object' && value.componentType && value.bindModelValue">
<component
:is="value.componentType"
v-bind="value.attrs"
v-model="props.getRow(table.rows, scope.row[props.rowKeyName], false)[col.name]"
></component>
</template>
<template v-else-if="!Tools.isEmpty(value) && typeof value === 'object' && value.componentType">
<component :is="value.componentType" v-bind="value.attrs"></component>
</template>
<template v-else>
<span v-dompurify-html="Tools.isUndefinedOrNull(value) ? '' : value"></span>
</template>
</template>
</q-td>
</template>
</template>
<script setup lang="ts">
import { inject, computed, ref, toRaw, onMounted } from 'vue';
import { Tools } from '@/platform';
import { selectMode, editStatus } from './ts/grid.ts';
const tdRef = ref();
const componentRef = ref();
const tdWidth = ref(0);
const props = defineProps({
grid: {
type: Object,
default: () => {},
},
getRow: {
type: Function,
default: () => {},
},
rowKeyName: {
type: String,
default: '',
},
scope: {
type: Object,
default: () => {
return {};
},
},
col: {
type: Object,
default: () => {
return {};
},
},
value: {
type: [Object, String, Number, Boolean],
default: '',
},
isSelectedRow: {
type: Boolean,
default: false,
},
});
const table = inject('table');
const getComponentRef = () => {
return componentRef.value;
};
const isSelectedCellComputed = computed(() => {
const selected = props.grid.getSelectedCell();
if (selected?.colName && props.scope.row[props.rowKeyName] === selected['row'][props.rowKeyName] && props.col['name'] === selected.colName) {
return true;
}
return false;
});
const titleComputed = computed(() => {
if (table['columns'] && table['columns'].length > 0) {
const column = table['columns'].find((item) => item['name'] === props.col.name);
if (column && column['title'] && typeof column['title'] === 'function') {
return column.title({ grid: props.grid, row: toRaw(props.scope.row), value: props.scope.row[props.col.name] });
} else if (column && column['title']) {
return column['title'];
}
}
if (props.col.classes?.indexOf('truncate') > -1 && !Tools.isEmpty(props.value) && typeof props.value !== 'object') {
return props.value;
}
return '';
});
const rowSpanComputed = computed(() => {
if (Object.keys(table.mergeRecords).length > 0) {
const column = table.mergeRecords[props.col['name']];
if (column && column[props.scope['row'][props.col['name']]].length > 1) {
return column[props.scope['row'][props.col['name']]].length;
}
}
return 1;
});
const rowSpanIsFirstComputed = computed(() => {
if (Object.keys(table.mergeRecords).length > 0) {
const column = table.mergeRecords[props.col['name']];
if (props.col['name'] === '_sortNo_') {
return true;
} else if (column && column[props.scope['row'][props.col['name']]].length > 1) {
return props.scope['row'][props.rowKeyName] === column[props.scope['row'][props.col['name']]][0];
}
}
return true;
});
const tdClassComputed = computed(() => {
const tdClass = <any>[];
if (props.grid.props.selectMode === selectMode.cell) {
tdClass.push('cellHover');
}
if (table && table['cellSelected'] && Tools.hasOwnProperty(table['cellSelected'], 'colName')) {
if (table['cellSelected']['colName'] === props.col['name'] && table['cellSelected']['rowKey'] === props.scope.row[props.rowKeyName])
tdClass.push('cellSelected');
}
return tdClass;
});
const componentAttrs = (col) => {
if (col.attrs) {
return col.attrs;
} else if (props.grid.props.editor?.form?.fields) {
const field = props.grid.props.editor.form.fields.find((item) => item['name'] === col.name);
if (field) {
return { ...field, label: undefined };
}
}
return undefined;
};
onMounted(() => {
if (tdRef.value?.$el) {
tdWidth.value = tdRef.value.$el.clientWidth;
}
});
defineExpose({
getComponentRef,
});
</script>
<style lang="css"></style>

786
io.sc.platform.core.frontend/src/platform/components/grid/GridTop.vue

@ -1,786 +0,0 @@
<template>
<div class="col">
<w-form ref="formRef" v-bind="props.grid.props.queryFormAttrs" :fields="queryFormFieldsComputed" :cols-num="props.grid.props.queryFormColsNum"></w-form>
<div
v-if="props.grid.props.title || buttons_.length > 0 || props.grid.props.configButton || fields.length > 0"
class="flex flex-nowrap items-end"
:class="fields.length > 0 ? 'pt-2.5' : ''"
>
<div class="flex-none">{{ $t(props.grid.props.title ? props.grid.props.title : '') }}</div>
<div class="flex-1">
<w-toolbar
ref="toolbarRef"
:dense="denseToolbarComputed"
v-bind="props.grid.props.toolbarConfigure"
:buttons="toolbarButtonsComputed"
:grid="props.grid"
></w-toolbar>
</div>
<div v-if="props.grid.props.configButton" class="flex-none pl-1">
<q-btn round dense :size="denseToolbarComputed ? '13px' : undefined" icon="manage_accounts" unelevated outline>
<q-popup-proxy v-model="table.gridConfig">
<GridConfig :scope="props.scope" :more-column-title-array="props.moreColumnTitleArray" :grid="props.grid"></GridConfig>
</q-popup-proxy>
</q-btn>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, inject, ref, reactive, nextTick, onBeforeMount, toRaw } from 'vue';
import { useQuasar, exportFile } from 'quasar';
import { axios, Tools, t, NotifyManager } from '@/platform';
import { selectMode, formStatus, editStatus } from './ts/grid';
import GridConfig from './GridConfig.vue';
const $q = useQuasar();
const formRef = ref();
const toolbarRef = ref();
const localeFlag = ref(false);
const fields = ref(<any>[]);
const buttons_ = ref(<any>[]);
const moreQueryStatus = ref(false); //
const props = defineProps({
grid: {
//
type: Object,
default: () => {
return {};
},
},
scope: {
//
type: Object,
default: () => {
return {};
},
},
moreColumnTitleArray: {
//
type: Array,
default: () => {
return [];
},
},
buildQueryCriterias: {
//
type: Function,
default: () => {},
},
columns: {
//
type: Array,
default: () => {
return [];
},
},
url: {
// url
type: Object,
default: () => {
return {};
},
},
});
const table = inject('table');
const queryFormFieldsComputed = computed(() => {
localeFlag.value;
return fields.value;
});
const toolbarButtonsComputed = computed(() => {
localeFlag.value;
return buttons_.value;
});
const denseToolbarComputed = computed(() => {
if (table.denseToolbar) {
return true;
} else if (table.dense !== false) {
return true;
} else {
return false;
}
});
const screenCols = { xs: 1, sm: 2, md: 3, lg: 4, xl: 6 };
const queryFormColsNumComputed = computed(() => {
if (typeof props.grid.props.queryFormColsNum === 'number' && props.grid.props.queryFormColsNum > 0) {
return props.grid.props.queryFormColsNum;
} else if (typeof props.grid.props.queryFormColsNum === 'object') {
const screen = { ...screenCols, ...props.grid.props.queryFormColsNum };
return screen[$q.screen.name];
}
return screenCols[$q.screen.name];
});
// form
const handlerQueryFormShowField = () => {
fields.value = [];
if (moreQueryStatus.value) {
props.grid.props.queryFormFields.forEach((item: any) => {
fields.value.push(item);
item.showIf = () => {
return true;
};
});
} else {
//
const rowColsNum = queryFormColsNumComputed.value * (props.grid.props.queryFormRowNum || 1);
let currRowColsNum = 0;
props.grid.props.queryFormFields.forEach((item: any) => {
if (Tools.hasOwnProperty(item, 'colSpan')) {
currRowColsNum += item.colSpan;
} else {
currRowColsNum += 1;
}
if (currRowColsNum <= rowColsNum) {
fields.value.push(item);
item.showIf = (form) => {
return true;
};
} else {
item.showIf = (form) => {
return false;
};
}
});
}
};
const edit = (selected) => {
if (!selected) {
NotifyManager.warn(t('action.edit.tip'));
} else {
openEditor(t('action.edit'), formStatus.edit, selected);
}
};
const clone = (selected) => {
if (!selected) {
NotifyManager.warn(t('action.copy.tip'));
} else {
selected[props.grid.props.primaryKey] = undefined;
openEditor(t('action.copy'), formStatus.clone, selected);
}
};
const remove = () => {
const ids = <any>[];
const tickedRows = props.grid.getTickedRows();
const selectedRows = props.grid.getSelectedRows();
if (tickedRows?.length > 0) {
tickedRows.forEach((item) => {
ids.push(props.grid.props.localMode ? item['_rowKey_'] : item[props.grid.props.primaryKey]);
});
} else if (selectedRows?.length > 0) {
selectedRows.forEach((item) => {
ids.push(props.grid.props.localMode ? item['_rowKey_'] : item[props.grid.props.primaryKey]);
});
}
let flag = true;
props.grid.emit('beforeRemove', {
grid: props.grid,
ids: ids,
callback: (param: any) => {
if (param && Array.isArray(param)) {
ids.splice(0, ids.length - 1);
ids.push(...param);
} else {
flag = false;
}
},
});
if (flag) {
if (props.grid.props.localMode) {
props.grid.removeLocalData(ids);
} else {
let requestParams: any = {
method: 'DELETE',
url: props.url.removeDataUrl || props.url.dataUrl,
data: ids,
};
axios(requestParams)
.then((resp) => {
props.grid.emit('afterRemove', { grid: props.grid, ids: resp?.data });
NotifyManager.info(t('tip.operationSuccess'));
if (props.grid.props.refreshData || !props.grid.props.tree) {
props.grid.refresh();
} else {
props.grid.removeLocalData(resp?.data);
}
})
.catch((error) => {
console.error('[w-grid]Remove error:', error);
});
}
}
};
const getExportData = async () => {
let resultData = <any>[];
const reqParams: any = { pageable: false };
let urlSearchParams = props.buildQueryCriterias(reqParams);
const resp = await axios.get(props.url.fetchDataUrl || props.url.dataUrl, { params: urlSearchParams });
if (resp && resp.data) {
const responseData = resp.data;
if (Array.isArray(responseData)) {
resultData = responseData;
} else if (typeof responseData === 'object' && responseData.content) {
resultData = responseData.content;
}
}
return resultData;
};
const wrapCsvValue = (val, formatFn, row) => {
let formatted = formatFn !== void 0 ? formatFn(val, row) : val;
formatted = formatted === void 0 || formatted === null ? '' : String(formatted);
formatted = formatted.split('"').join('""');
/**
* Excel accepts \n and \r in strings, but some other CSV parsers do not
* Uncomment the next two lines to escape new lines
*/
// .split('\n').join('\\n')
// .split('\r').join('\\r')
return `"${formatted}"`;
};
const resetDefaultValues = () => {
let requestParams: any = {
method: 'POST',
url: props.url.dataUrl + '/resetDefaultValues',
};
axios(requestParams)
.then((resp) => {
NotifyManager.info(t('tip.operationSuccess'));
props.grid.refresh();
})
.catch((error) => {
console.error('[w-grid]ResetDefaultValues error:', error);
});
};
const showLoading = (msg: string = '正在处理,请稍等...') => {
$q.loading.show({
message: msg,
boxClass: 'bg-grey-2 text-grey-9',
spinnerColor: 'primary',
});
};
const hideLoading = () => {
$q.loading.hide();
};
const openEditor = (title, status, data) => {
props.grid.getEditorDialog().show();
nextTick(() => {
props.grid.getEditorDialog().setTitle(title);
props.grid.getEditorForm().setStatus(status);
if (!Tools.isEmpty(data)) {
props.grid.getEditorForm().setData(data);
}
});
};
const buttonObj = reactive({
separator: 'separator',
query: {
name: 'query',
icon: 'search',
labelI18nKey: 'action.query',
label: t('action.query'),
click: () => {
props.grid.refresh();
},
},
moreQuery: {
name: 'moreQuery',
icon: 'zoom_in',
labelI18nKey: 'action.moreQueryConditions',
label: t('action.moreQueryConditions'),
enableIf: () => {
if (props.grid.props.queryFormFields.length <= fields.value.length && !moreQueryStatus.value) {
return false;
} else {
return true;
}
},
click: () => {
moreQueryStatus.value = !moreQueryStatus.value;
handlerQueryFormShowField();
},
},
reset: {
name: 'reset',
icon: 'restart_alt',
labelI18nKey: 'action.reset',
label: t('action.reset'),
click: () => {
formRef.value.reset();
},
},
refresh: {
name: 'refresh',
icon: 'loop',
labelI18nKey: 'action.refresh',
label: t('action.refresh'),
click: () => {
props.grid.refresh();
},
},
add: {
name: 'add',
icon: 'add',
labelI18nKey: 'action.addNew',
label: t('action.addNew'),
click: () => {
openEditor(t('action.addNew'), formStatus.add, undefined);
},
afterEditorOpen: () => {
props.grid.emit('afterEditorOpen', { grid: props.grid, data: undefined });
},
},
edit: {
name: 'edit',
icon: 'edit',
labelI18nKey: 'action.edit',
label: t('action.edit'),
enableIf: (args) => {
if (args.selected) {
return true;
}
return false;
},
click: (args) => {
edit(args.selected);
},
afterEditorOpen: (args) => {
props.grid.emit('afterEditorOpen', { grid: props.grid, data: args.selected });
},
},
inlineCellEdit: {
name: 'inlineCellEdit',
icon: 'border_color',
labelI18nKey: 'action.edit',
label: t('action.edit'),
enableIf: (args) => {
if (Object.keys(table.cellSelected).length > 0 && table.cellSelected['colName']) {
return true;
}
return false;
},
click: () => {
if (props.grid.props.selectMode !== selectMode.cell) {
console.warn('[w-grid]`selectMode` property is not `cell`, Cannot use cell editing function.');
return false;
} else if (Object.keys(table.cellSelected).length === 0 || !table.cellSelected['colName']) {
NotifyManager.info('请选择要编辑的单元格');
return false;
} else if (table.columns.findIndex((item) => item['name'] === table.cellSelected['colName'] && item['type']) < 0) {
console.warn('[w-grid]The column selected is not configured with a component type for editing.');
return false;
}
table.bodyEditStatus = editStatus.cell;
},
},
cellEdit: {
name: 'cellEdit',
icon: 'border_color',
labelI18nKey: 'action.edit',
label: t('action.edit'),
enableIf: (args) => {
if (Object.keys(table.cellSelected).length > 0 && table.cellSelected['colName']) {
return true;
}
return false;
},
click: () => {
if (props.grid.props.selectMode !== selectMode.cell) {
console.warn('[w-grid]`selectMode` property is not `cell`, Cannot use cell editing function.');
return false;
} else if (Object.keys(table.cellSelected).length === 0 || !table.cellSelected['colName']) {
NotifyManager.info('请选择要编辑的单元格');
return false;
} else if (table.columns.findIndex((item) => item['name'] === table.cellSelected['colName'] && item['type']) < 0) {
console.warn('[w-grid]The column selected is not configured with a component type for editing.');
return false;
}
//
props.grid.getCellEditorDialog().show();
},
},
inlineRowEdit: {
name: 'inlineRowEdit',
icon: 'edit_note',
labelI18nKey: 'action.edit',
label: t('action.edit'),
enableIf: (args) => {
if (args.selected) {
return true;
}
return false;
},
click: () => {
if (table.columns.findIndex((item) => item['type']) < 0) {
console.warn('[w-grid]Not configured with a component type for editing.');
return false;
}
table.bodyEditStatus = editStatus.row;
},
},
inlineRowsEdit: {
name: 'inlineRowsEdit',
icon: 'app_registration',
labelI18nKey: 'action.edit',
label: t('action.edit'),
click: () => {
if (table.columns.findIndex((item) => item['type']) < 0) {
console.warn('[w-grid]Not configured with a component type for editing.');
return false;
}
table.bodyEditStatus = editStatus.rows;
//
props.grid.clearSelected();
props.grid.clearTicked();
table.allTicked = false;
},
},
clone: {
name: 'clone',
icon: 'content_copy',
labelI18nKey: 'action.copy',
label: t('action.copy'),
enableIf: (args) => {
if (args.selected) {
return true;
}
return false;
},
click: () => {
clone(props.grid.getSelectedRow());
},
afterEditorOpen: (args) => {
props.grid.emit('afterEditorOpen', { grid: props.grid, data: args.selected });
},
},
remove: {
name: 'remove',
icon: 'delete',
labelI18nKey: 'action.remove',
label: t('action.remove'),
enableIf: (args) => {
if (args.ticked) {
return true;
} else if (args.selected) {
return true;
}
return false;
},
click: (tips: boolean = true) => {
if (!tips) {
remove();
} else {
$q.dialog({
title: t('confirm'),
message: t('action.remove.tip'),
cancel: { noCaps: true },
ok: { noCaps: true },
persistent: true,
}).onOk(() => {
remove();
});
}
},
},
view: {
name: 'view',
icon: 'visibility',
labelI18nKey: 'action.view',
label: t('action.view'),
enableIf: (args) => {
if (args.selected) {
return true;
}
return false;
},
click: () => {
props.grid.view();
},
},
export: {
name: 'export',
icon: 'file_download',
labelI18nKey: 'action.export',
label: t('action.export'),
click: async () => {
showLoading();
let exportData = props.grid.getRows();
// url
if (!Tools.isEmpty(props.grid.props.fetchDataUrl) || !Tools.isEmpty(props.grid.props.dataUrl)) {
const fetchResult = await getExportData();
if (fetchResult && fetchResult.length > 0) {
exportData = fetchResult;
}
}
const content = [props.columns.map((col) => wrapCsvValue(col.label))]
.concat(
exportData.map((row) =>
props.columns
.map((col) => wrapCsvValue(typeof col.field === 'function' ? col.field(row) : row[col.field === void 0 ? col.name : col.field], col.format, row))
.join(','),
),
)
.join('\r\n');
const status = exportFile('table-export.csv', content, {
encoding: 'utf-8',
mimeType: 'text/csv',
byteOrderMark: '\uFEFF', //
});
if (status !== true) {
NotifyManager.error(t('action.export.failed'));
}
hideLoading();
},
},
addTop: {
name: 'addTop',
icon: 'add',
labelI18nKey: 'action.addTop',
label: t('action.addTop'),
click: () => {
openEditor(t('action.addTop'), formStatus.addTop, undefined);
},
afterEditorOpen: () => {
props.grid.emit('afterEditorOpen', { grid: props.grid, data: undefined });
},
},
addChild: {
name: 'addChild',
icon: 'playlist_add',
labelI18nKey: 'action.addChild',
label: t('action.addChild'),
enableIf: (args) => {
if (args.selected) {
return true;
}
return false;
},
click: () => {
openEditor(t('action.addChild'), formStatus.addChild, undefined);
},
afterEditorOpen: () => {
props.grid.emit('afterEditorOpen', { grid: props.grid, data: undefined });
},
},
expand: {
name: 'expand',
icon: () => {
return table.treeExpand ? 'expand_less' : 'expand_more';
},
label: () => {
return table.treeExpand ? t('action.collapseAll') : t('action.expandAll');
},
click: () => {
if (table.treeExpand) {
props.grid.collapse();
} else {
props.grid.expand();
}
table.treeExpand = !table.treeExpand;
},
},
resetDefaultValues: {
name: 'resetDefaultValues',
icon: 'bi-copy',
labelI18nKey: 'action.resetDefaultValues',
label: t('action.resetDefaultValues'),
click: (tips: boolean = true) => {
if (!tips) {
resetDefaultValues();
} else {
$q.dialog({
title: t('confirm'),
message: t('action.resetDefaultValues.tip'),
cancel: { noCaps: true },
ok: { noCaps: true },
persistent: true,
}).onOk(() => {
resetDefaultValues();
});
}
},
},
});
// toobar
const handleChildrenBtn = (arr, moreQueryShow) => {
const tempArr = <any>[];
for (let i = 0; i < arr.length; i++) {
const btn = arr[i];
if (typeof btn === 'string' && !buttonObj[btn]) {
throw new Error('[w-grid]`' + btn + '`' + ' toolbar action NOT exist.');
} else if (Array.isArray(btn) && btn.length > 0) {
const handleResult = handleChildrenBtn(btn, moreQueryShow);
if (handleResult && handleResult.length > 0) {
tempArr.push(handleResult);
}
} else if (typeof btn === 'string' && buttonObj[btn]) {
if (btn === buttonObj.query.name && i === 0) {
// QBtnDropdown
// ['query', 'reset']
tempArr.push(buttonObj[btn]);
tempArr.push(buttonObj[buttonObj.moreQuery.name]);
} else if (btn === buttonObj.query.name) {
tempArr.push([buttonObj[btn], buttonObj[buttonObj.moreQuery.name]]);
} else {
tempArr.push(buttonObj[btn]);
}
} else if (typeof btn === 'object' && btn.extend && buttonObj[btn.extend]) {
let overrideClick = false;
if (btn.click) {
overrideClick = true;
}
tempArr.push({ ...buttonObj[btn.extend], ...btn, _click: buttonObj[btn.extend].click, overrideClick });
} else {
tempArr.push(btn);
}
}
return tempArr;
};
const handleToolbarActions = () => {
buttons_.value.splice(0, buttons_.value.length);
//
let moreQueryShow = false;
const rowColsNum = queryFormColsNumComputed.value * (props.grid.props.queryFormRowNum || 1);
let currRowColsNum = 0;
let showQueryFormFieldNum = 0;
props.grid.props.queryFormFields.forEach((item: any) => {
if (Tools.hasOwnProperty(item, 'colSpan')) {
currRowColsNum += item.colSpan;
} else {
currRowColsNum += 1;
}
if (currRowColsNum <= rowColsNum) {
showQueryFormFieldNum += 1;
}
});
if (showQueryFormFieldNum < props.grid.props.queryFormFields.length) {
moreQueryShow = true;
}
props.grid.props.toolbarActions.forEach((btn: any, index) => {
if (typeof btn === 'string' && !buttonObj[btn]) {
throw new Error('[w-grid]`' + btn + '`' + ' toolbar action NOT exist.');
} else if (typeof btn === 'string' && buttonObj[btn]) {
if (btn === buttonObj.query.name && moreQueryShow) {
buttons_.value.push([buttonObj[btn], buttonObj[buttonObj.moreQuery.name]]);
} else {
buttons_.value.push(buttonObj[btn]);
}
} else if (Array.isArray(btn) && btn.length > 0) {
buttons_.value.push(handleChildrenBtn(btn, moreQueryShow));
} else if (typeof btn === 'object' && btn.extend && buttonObj[btn.extend]) {
//
let overrideClick = false;
if (btn.click) {
overrideClick = true;
}
buttons_.value.push({ ...buttonObj[btn.extend], ...btn, _click: buttonObj[btn.extend].click, overrideClick: overrideClick });
} else {
buttons_.value.push(btn);
}
});
if (buttons_.value.length > 0 && buttons_.value[buttons_.value.length - 1] !== 'separator' && props.grid.props.configButton) {
buttons_.value.push(buttonObj.separator);
}
};
const setLocaleFlag = () => {
localeFlag.value = !localeFlag.value;
};
const resetLabel = () => {
Object.keys(buttonObj).forEach((btn) => {
if (typeof buttonObj[btn] === 'object' && buttonObj[btn].labelI18nKey) {
buttonObj[btn].label = t(buttonObj[btn].labelI18nKey);
}
});
};
const buttonClick = (arr) => {
arr.forEach((button) => {
if (typeof button === 'object' && !Tools.isEmpty(button.click) && props.grid.props.dbClickOperation === button.name) {
toolbarRef.value.buttonClick(button);
} else if (Array.isArray(button) && button.length > 0) {
buttonClick(button);
}
});
};
const dbClickOperation = (row) => {
if (!Tools.isEmpty(row) && props.grid.props.dbClickOperation === buttonObj.expand.name) {
row['expand'] = Tools.isEmpty(row['expand']) ? true : !row['expand'];
} else {
if (!buttonObj[props.grid.props.dbClickOperation] && checkConfigNotContains(buttons_.value)) {
throw new Error('[w-grid]`' + props.grid.props.dbClickOperation + '`' + ' toolbar action NOT exist.');
} else if (
props.grid.props.dbClickOperation !== buttonObj.separator &&
buttonObj[props.grid.props.dbClickOperation] &&
checkConfigNotContains(buttons_.value)
) {
// dbClickOperation
toolbarRef.value.buttonClick(buttonObj[props.grid.props.dbClickOperation]);
} else {
buttonClick(buttons_.value);
}
}
};
//
const checkConfigNotContains = (arr) => {
let flag = true;
for (let i = 0; i < arr.length; i++) {
if (typeof arr[i] === 'object' && props.grid.props.dbClickOperation === arr[i]['name']) {
flag = false;
break;
} else if (Array.isArray(arr[i]) && arr[i].length > 0) {
const tempFlag = checkConfigNotContains(arr[i]);
flag = tempFlag;
}
}
return flag;
};
const getQueryForm = () => {
return formRef.value;
};
onBeforeMount(() => {
handleToolbarActions();
});
defineExpose({
setLocaleFlag,
handlerQueryFormShowField,
handleToolbarActions,
resetLabel,
dbClickOperation,
getQueryForm,
reset: buttonObj.reset.click,
add: buttonObj.add.click,
edit,
cellEdit: buttonObj.cellEdit.click,
inlineCellEdit: buttonObj.inlineCellEdit.click,
inlineRowEdit: buttonObj.inlineRowEdit.click,
inlineRowsEdit: buttonObj.inlineRowsEdit.click,
clone: buttonObj.clone.click,
remove: buttonObj.remove.click,
view: buttonObj.view.click,
export: buttonObj.export.click,
addTop: buttonObj.addTop.click,
addChild: buttonObj.addChild.click,
expand: buttonObj.expand.click,
resetDefaultValues: buttonObj.resetDefaultValues.click,
});
</script>
<style lang="css"></style>

100
io.sc.platform.core.frontend/src/platform/components/grid/GridView.vue

@ -1,100 +0,0 @@
<template>
<w-drawer ref="drawerRef" :title="$t('action.view')" v-bind="props.grid.props.viewer.drawer">
<div class="p-2.5">
<w-info-panel ref="infoRef" v-bind="props.grid.props.viewer.panel" :info="infoArray"></w-info-panel>
</div>
</w-drawer>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { t, Tools, NotifyManager } from '@/platform';
const drawerRef = ref();
const infoRef = ref();
const props = defineProps({
grid: {
//
type: Object,
default: () => {
return {};
},
},
tableColumnsMap: {
type: Map,
default: () => {
return new Map();
},
},
});
const infoArray = ref(<any>[]);
const view = () => {
const selected = props.grid.getSelectedRow();
if (!selected) {
NotifyManager.warn(t('action.view.tip'));
} else {
infoArray.value = [];
if (props.grid.props.viewer.panel.fields && props.grid.props.viewer.panel.fields.length > 0) {
for (let item of props.grid.props.viewer.panel.fields) {
if (item.format) {
let value = selected[item.name];
try {
value = item.format(selected[item.name], selected[0]);
} catch (error) {
console.error('format error!');
}
infoArray.value.push({ label: item.label, value: value, originalValue: selected[item.name] });
} else {
let value = null;
if (props.tableColumnsMap.get(item.name) && !Tools.isEmpty(props.tableColumnsMap.get(item.name).format)) {
value = selected[item.name];
try {
value = props.tableColumnsMap.get(item.name).format(selected[item.name], selected);
} catch (error) {
console.error('format error!');
}
} else {
value = selected[item.name];
}
infoArray.value.push({ label: item.label, value: value, originalValue: selected[item.name] });
}
}
} else {
for (let item of props.tableColumnsMap) {
if (item[1].format) {
let value = selected[item[0]];
try {
value = item[1].format(selected[item[0]], selected);
} catch (error) {
console.error('format error!');
}
infoArray.value.push({ label: item[1].label, value: value, originalValue: selected[item.name] });
} else {
infoArray.value.push({
label: item[1].label,
value: selected[item[0]],
originalValue: selected[item.name],
});
}
}
}
drawerRef.value.show();
}
};
const getViewerDrawer = () => {
return drawerRef.value;
};
const getInfoPanel = () => {
return infoRef.value;
};
defineExpose({
getViewerDrawer,
getInfoPanel,
view,
});
</script>
<style lang="css"></style>

171
io.sc.platform.core.frontend/src/platform/components/grid/GridHeader.vue → io.sc.platform.core.frontend/src/platform/components/grid/Header.vue

@ -1,24 +1,22 @@
<template>
<template v-if="columnTitleState.columnTitleRowNum > 1">
<!-- 多表头 -->
<q-tr v-for="(r, rIndex) in columnTitleState.columnTitleArr" :key="rIndex">
<q-th
v-if="
rIndex === 0 && props.selection === 'multiple' && table.checkboxSelection && !props.grid.props.tree && props.grid.props.selectMode !== selectMode.none
"
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"
>
<q-checkbox v-model="table.allTicked" flat :dense="props.denseHeader" @update:model-value="allTickedUpdateFun" />
<q-checkbox
v-model="tools.table.store.headerTicked"
flat
:dense="tools.cm.denseHeader.value"
@update:model-value="tools.em.updateTickeds(tools.table.store.headerTicked)"
/>
</q-th>
<q-th
v-else-if="rIndex === 0 && table.checkboxSelection && !props.grid.props.tree && props.grid.props.selectMode !== selectMode.none"
:rowspan="columnTitleState.columnTitleRowNum"
:style="moreColumnTitleTableSelectionStyle"
class="firstColumn"
></q-th>
<q-th
v-if="rIndex === 0 && table.sortNo && !props.grid.props.tree"
v-if="rIndex === 0 && tools.table.configStore.showSortNo && !tools.props.tree"
:rowspan="columnTitleState.columnTitleRowNum"
:style="moreColumnTitleTableSortNoStyle"
:class="rowNumIsFirstColumnComputed ? 'firstColumn' : ''"
@ -39,24 +37,25 @@
<span v-dompurify-html="Tools.isUndefinedOrNull(c.label) ? '' : c.label"></span>
</q-th>
</q-tr>
<q-tr v-if="table.rows.length === 0" :style="props.noDataTrHeightStyle" class="noDataTr">
<q-td :colspan="props.noDataTrColspan" align="center" valian="middle" style="border-bottom-width: 0px"
<q-tr v-if="tools.table.rows.length === 0" :style="tools.cm.noDataStyle.value" class="noDataTr">
<q-td :colspan="tools.cm.displayColumnNum.value" align="center" valian="middle" style="border-bottom-width: 0px"
><q-icon size="2em" name="info" />{{ $t('tip.noData') }}</q-td
>
</q-tr>
</template>
<template v-else>
<!-- 常规表头 -->
<q-tr :props="scope">
<q-th
v-if="props.selection === 'multiple' && table.checkboxSelection && !props.grid.props.tree && props.grid.props.selectMode !== selectMode.none"
:style="props.grid.props.tree ? '' : 'padding: 0; min-width: 50px;width: 50px;max-width:50px'"
v-if="tools.table.configStore.useCheckboxSelection && !tools.props.tree && tools.props.selectMode !== Constant.SELECT_MODE.NONE"
:style="tools.props.tree ? '' : 'padding: 0; min-width: 50px;width: 50px;max-width:50px'"
>
<q-checkbox v-model="table.allTicked" flat :dense="props.denseHeader" @update:model-value="allTickedUpdateFun"
<q-checkbox
v-model="tools.table.store.headerTicked"
flat
:dense="tools.cm.denseHeader.value"
@update:model-value="tools.em.updateTickeds(tools.table.store.headerTicked)"
/></q-th>
<q-th
v-else-if="table.checkboxSelection && !props.grid.props.tree && props.grid.props.selectMode !== selectMode.none"
:style="props.grid.props.tree ? '' : 'padding: 0; min-width: 50px;width: 50px;max-width:50px'"
></q-th>
<template v-for="col in scope.cols" :key="col.name">
<q-th
:props="scope"
@ -69,26 +68,20 @@
</q-th>
</template>
</q-tr>
<q-tr v-if="table.rows.length === 0" :style="props.noDataTrHeightStyle" class="noDataTr">
<q-td :colspan="props.noDataTrColspan" align="center" valian="middle" style="border-bottom-width: 0px"
><q-icon size="2em" name="info" />{{ $t('tip.noData') }}</q-td
<q-tr v-if="tools.table.rows.length === 0" :style="tools.cm.noDataStyle.value" class="noDataTr">
<q-td :colspan="tools.cm.displayColumnNum.value" align="center" valian="middle" style="border-bottom-width: 0px"
><q-icon size="2em" name="info" />{{ $t(tools.table.store.noDataLabelI18nKey) }}</q-td
>
</q-tr>
</template>
</template>
<script setup lang="ts">
import { Tools } from '@/platform';
import { Tools, $t } from '@/platform';
import { computed, inject, reactive } from 'vue';
import { selectMode } from './ts/grid.ts';
import { Constant, GridTools } from './ts/index';
const tools = <GridTools>inject('tools');
const props = defineProps({
grid: {
//
type: Object,
default: () => {
return {};
},
},
scope: {
//
type: Object,
@ -96,46 +89,7 @@ const props = defineProps({
return {};
},
},
selection: {
type: String,
default: '',
},
denseHeader: {
type: Boolean,
default: false,
},
rawColumns: {
type: Array,
default: () => {
return [];
},
},
tableColumns: {
type: Array,
default: () => {
return [];
},
},
excludeColumnNum: {
type: Number,
default: () => {
return 0;
},
},
noDataTrHeightStyle: {
type: Object,
default: () => {
return {};
},
},
noDataTrColspan: {
type: Number,
default: () => {
return 0;
},
},
});
const table = inject('table');
//
const columnTitleState = reactive({
@ -161,8 +115,8 @@ let moreColumnTitleMap = new Map<string, MoreColumnTitleType>();
let allColumnMap = new Map();
const moreColumnTitleTableSelectionStyle = computed(() => {
if (table.stickyNum > 0) {
if (props.grid.props.tree) {
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;';
@ -172,31 +126,27 @@ const moreColumnTitleTableSelectionStyle = computed(() => {
});
const moreColumnTitleTableSortNoStyle = computed(() => {
if (table.checkboxSelection && table.sortNo && table.stickyNum > 0) {
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 (table.sortNo && table.stickyNum > 0) {
} 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 (props.selection === 'multiple' && table.checkboxSelection && !props.grid.props.tree && props.grid.props.selectMode !== selectMode.none) {
return false;
} else if (table.checkboxSelection && !props.grid.props.tree && props.grid.props.selectMode !== selectMode.none) {
if (tools.table.configStore.useCheckboxSelection && !tools.props.tree && tools.props.selectMode !== Constant.SELECT_MODE.NONE) {
return false;
} else if (table.sortNo && !props.grid.props.tree) {
} else if (tools.table.configStore.showSortNo && !tools.props.tree) {
return true;
} else {
return false;
}
});
const isFirstColumn = (c, cIndex) => {
if (props.selection === 'multiple' && table.checkboxSelection && !props.grid.props.tree && props.grid.props.selectMode !== selectMode.none) {
return '';
} else if (table.checkboxSelection && !props.grid.props.tree && props.grid.props.selectMode !== selectMode.none) {
if (tools.table.configStore.useCheckboxSelection && !tools.props.tree && tools.props.selectMode !== Constant.SELECT_MODE.NONE) {
return '';
} else if (table.sortNo && !props.grid.props.tree) {
} else if (tools.table.configStore.showSortNo && !tools.props.tree) {
return '';
} else if (isFirstHandle(c)) {
return 'firstColumn';
@ -206,7 +156,7 @@ const isFirstColumn = (c, cIndex) => {
};
const isFirstHandle = (c) => {
let isFirst = false;
const firstColumn = props.rawColumns[0];
const firstColumn = tools.table.originalColumns[0];
if (firstColumn?.name === c['name']) {
isFirst = true;
} else if (firstColumn?.columns?.length > 0) {
@ -226,27 +176,6 @@ const child = (arr, c) => {
return isExist;
};
const allTickedUpdateFun = (value, evt) => {
if (table.bodyEditStatus === 'none') {
if (value) {
table.rows.forEach((item) => {
item[table.tickedField] = true;
item[table.selectedField] = true;
});
} else {
table.rows.forEach((item) => {
item[table.tickedField] = false;
item[table.selectedField] = false;
});
}
} else if (table.bodyEditStatus === 'rowEdit' || table.bodyEditStatus === 'cellEdit') {
table.allTicked = null;
} else if (table.bodyEditStatus === 'rowsEdit') {
table.allTicked = false;
}
props.grid.emit('updateTickeds', { grid: props.grid, value: value });
};
const handlerStickyChildrenColumn = (item, columns) => {
columns.push(item);
if (item.columns && item.columns.length > 0) {
@ -257,8 +186,8 @@ const handlerStickyChildrenColumn = (item, columns) => {
};
const getStickyColumn = () => {
const columns = props.rawColumns.filter((item, index) => {
return index < table.stickyNum;
const columns = tools.table.originalColumns.filter((item, index) => {
return index < tools.table.configStore.stickyNum;
});
const arr = [];
columns.forEach((item) => {
@ -323,7 +252,7 @@ const thStyleHandler = (c: any, scope: any) => {
}
const stickyColumnArr = getStickyColumn();
if (
table.stickyNum > 0 &&
tools.table.configStore.stickyNum > 0 &&
stickyColumnArr.findIndex((item: any) => {
return item.name === c.name;
}) > -1
@ -343,7 +272,7 @@ const thStyleHandler = (c: any, scope: any) => {
c.parents.every((_b) => tdArr[td].parents.some((_a) => _a === _b));
if (result) {
if (tr === 0) {
stickyThArr.push({ trIndex: tr + 1, tdIndex: td + props.excludeColumnNum + 1 });
stickyThArr.push({ trIndex: tr + 1, tdIndex: td + tools.cm.extColumnNum.value + 1 });
} else {
stickyThArr.push({ trIndex: tr + 1, tdIndex: td + 1 });
}
@ -374,9 +303,9 @@ const thStyleHandler = (c: any, scope: any) => {
return item === parent;
}) > -1
) {
stickyThArr.push({ trIndex: parentTrtdIndex.trIndex, tdIndex: td + props.excludeColumnNum + 1 });
stickyThArr.push({ trIndex: parentTrtdIndex.trIndex, tdIndex: td + tools.cm.extColumnNum.value + 1 });
} else {
stickyThArr.push({ trIndex: parentTrtdIndex.trIndex, tdIndex: td + props.excludeColumnNum + 1 });
stickyThArr.push({ trIndex: parentTrtdIndex.trIndex, tdIndex: td + tools.cm.extColumnNum.value + 1 });
}
}
}
@ -386,13 +315,13 @@ const thStyleHandler = (c: any, scope: any) => {
}
} else {
if (trtdIndex.tdIndex === 1) {
if (props.excludeColumnNum === 2) {
if (tools.cm.extColumnNum.value === 2) {
return thStickyLastNameComputed.value.findIndex((item) => {
return item.name === c.name;
}) > -1
? (style += 'z-index: 3;position: sticky;left: calc(var(--columnWidth-1-1) + var(--columnWidth-1-2));')
: (style += 'z-index: 3;position: sticky;left: calc(var(--columnWidth-1-1) + var(--columnWidth-1-2));');
} else if (props.excludeColumnNum === 1) {
} else if (tools.cm.extColumnNum.value === 1) {
return thStickyLastNameComputed.value.findIndex((item) => {
return item.name === c.name;
}) > -1
@ -407,14 +336,14 @@ const thStyleHandler = (c: any, scope: any) => {
}
} else {
for (let i = 1; i < trtdIndex.tdIndex; i++) {
stickyThArr.push({ trIndex: 1, tdIndex: i + props.excludeColumnNum });
stickyThArr.push({ trIndex: 1, tdIndex: i + tools.cm.extColumnNum.value });
}
}
}
if (props.excludeColumnNum === 2) {
if (tools.cm.extColumnNum.value === 2) {
stickyThArr.push({ trIndex: 1, tdIndex: 1 });
stickyThArr.push({ trIndex: 1, tdIndex: 2 });
} else if (props.excludeColumnNum === 1) {
} else if (tools.cm.extColumnNum.value === 1) {
stickyThArr.push({ trIndex: 1, tdIndex: 1 });
}
if (stickyThArr && stickyThArr.length > 0) {
@ -427,15 +356,15 @@ const thStyleHandler = (c: any, scope: any) => {
}
}
}
if (c['name'] === table.columns[table.columns.length - 1]['name']) {
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 (props.tableColumns?.length > 0) {
const i = props.tableColumns.findIndex((item: any) => item['name'] === column.name);
if (tools.table.columns?.length > 0) {
const i = tools.table.columns.findIndex((item: any) => item['name'] === column.name);
if (i > -1) {
return scope;
}
@ -521,7 +450,7 @@ function findParents(arrData: any, name: any) {
const handlerMoreRowColumnTitle = () => {
moreColumnTitleMap = new Map<string, MoreColumnTitleType>();
allColumnMap = new Map();
props.rawColumns.forEach((tableColumn: any) => {
tools.table.originalColumns.forEach((tableColumn: any) => {
columnToMap(tableColumn);
});
let maxColumnChildrenLevel = 0;
@ -541,7 +470,7 @@ const handlerMoreRowColumnTitle = () => {
tmpColspan = 0;
// parent
const parent = findParents(props.rawColumns, key);
const parent = findParents(tools.table.originalColumns, key);
moreColumnTitleMap.get(key)!.parents = parent;
// parentLevel

41
io.sc.platform.core.frontend/src/platform/components/grid/Pagination.vue

@ -0,0 +1,41 @@
<template>
<template v-if="tools.props.pageable && !tools.props.tree">
<q-pagination
v-model="page"
:boundary-links="tools.table.store.pagination.config.boundaryLinks"
:boundary-numbers="tools.table.store.pagination.config.boundaryNumbers"
:direction-links="tools.table.store.pagination.config.directionLinks"
:ellipses="tools.table.store.pagination.config.ellipses"
:max-pages="tools.table.store.location.middleWidth > 600 ? tools.table.store.pagination.config.maxPages : 3"
:min="1"
:max="props.scope.pagesNumber"
:size="tools.cm.denseBottom.value ? '10px' : ''"
@update:model-value="pageChange"
/>
<span>{{ $t('tip.pagenation.totalRecord', { count: tools.table.store.pagination.rowsNumber }) }}</span>
</template>
<template v-else> {{ $t('tip.pagenation.totalRecord', { count: tools.table.store.pagination.rowsNumber }) }} </template>
</template>
<script setup lang="ts">
import { inject } from 'vue';
import { $t } from '@/platform';
import type { GridTools } from './ts/index';
const tools = <GridTools>inject('tools');
const page = defineModel({ type: Number, default: 1 });
const props = defineProps({
scope: {
//
type: Object,
default: () => {
return {};
},
},
});
const pageChange = (value) => {
tools.reqApiFM.fetchData({ pagination: tools.table.store.pagination });
page.value = value;
};
</script>
<style lang="css"></style>

200
io.sc.platform.core.frontend/src/platform/components/grid/Td.vue

@ -0,0 +1,200 @@
<template>
<template v-if="generateTdIfComputed">
<q-td
ref="tdRef"
:key="props.col.name"
:props="props.scope"
:class="tdClassComputed"
:style="tdStyleComputed"
:title="titleComputed"
:rowspan="rowSpanComputed"
@click="tdClick"
>
<!-- 前端排序号 -->
<template v-if="props.col.name === Constant.FIELD_NAMES.SORT_NO">
{{ props.scope.rowIndex + 1 }}
</template>
<!-- 树表格第一列 -->
<template v-else-if="isTreeGridAndFirstTdComputed">
<TreeGridFirstTdContent
ref="treeGridFirstTdRef"
:value="props.col.value"
:row="props.scope.row"
:col="props.col"
:level="props.level"
></TreeGridFirstTdContent>
</template>
<!-- 内联编辑模式组件 -->
<template v-else-if="showInlineEditComponentComputed">
<InlineEditComponent :col="props.col" :row="props.scope.row"></InlineEditComponent>
</template>
<!-- 普通单元格内容 -->
<TdContent v-else :value="props.col.value"></TdContent>
</q-td>
</template>
</template>
<script setup lang="ts">
import { computed, inject, onMounted, ref, toRaw } from 'vue';
import { Tools } from '@/platform';
import TdContent from './TdContent.vue';
import TreeGridFirstTdContent from './TreeGridFirstTdContent.vue';
import InlineEditComponent from './extra/inline-edit/InlineEditComponent.vue';
import { Constant, GridTools } from './ts/index';
const tools = <GridTools>inject('tools');
const tdRef = ref();
const treeGridFirstTdRef = ref();
const tdWidth = ref(0);
const props = defineProps({
level: { type: Number, default: 0 },
col: {
type: Object,
default: () => {
return {};
},
},
scope: {
type: Object,
default: () => {
return {};
},
},
});
const rowKey = Constant.FIELD_NAMES.ROW_KEY;
const titleComputed = computed(() => {
if (tools.table.columns.length > 0) {
const column = tools.table.columns.find((item) => item['name'] === props.col.name);
if (column && column['title'] && typeof column['title'] === 'function') {
return column.title({ grid: tools.instance, row: toRaw(props.scope.row), value: props.scope.row[props.col.name] });
} else if (column && column['title']) {
return column['title'];
}
}
if (props.col.classes?.indexOf('truncate') > -1 && !Tools.isEmpty(props.col.value) && typeof props.col.value !== 'object') {
return props.col.value;
}
return '';
});
const tdClassComputed = computed(() => {
const tdClass = <any>[];
if (props.col.name === Constant.FIELD_NAMES.SORT_NO) {
tdClass.push('sortNo_td');
}
if (tools.props.selectMode === Constant.SELECT_MODE.CELL) {
tdClass.push('cellHover');
}
if (
tools.table.store.cellSelected &&
tools.table.store.cellSelected.colName === props.col['name'] &&
tools.table.store.cellSelected.rowKey === props.scope.row[rowKey]
) {
tdClass.push('cellSelected');
}
return tdClass;
});
const tdStyleComputed = computed(() => {
let style = {};
if (tools.editFM.getEditorFieldByName(props.col['name']) && tools.table.store.inlineEditStatus !== Constant.EDIT_STATUS.NONE) {
style['width'] = tdWidth.value - 10 + 'px';
}
return style;
});
const isTreeGridAndFirstTdComputed = computed(() => {
return tools.props.tree && props.col.name === props.scope.cols[0].name;
});
const showInlineEditComponentComputed = computed(() => {
return tools.table.store.inlineEditStatus !== Constant.EDIT_STATUS.NONE && tools.editFM.isShowInlineEditor(props.col, props.scope.row[rowKey]);
});
const rowSpanComputed = computed(() => {
if (Object.keys(tools.table.store.mergeGroupRecords).length > 0) {
const column = tools.table.store.mergeGroupRecords[props.col['name']];
if (column && column[props.scope['row'][props.col['name']]].length > 1) {
return column[props.scope['row'][props.col['name']]].length;
}
}
return 1;
});
const generateTdIfComputed = computed(() => {
if (Object.keys(tools.table.store.mergeGroupRecords).length > 0) {
const colName = props.col['name'];
const column = tools.table.store.mergeGroupRecords[colName];
if (colName === Constant.FIELD_NAMES.SORT_NO) {
return true;
} else if (column && column[props.scope['row'][colName]].length > 1) {
return props.scope['row'][rowKey] === column[props.scope['row'][colName]][0];
}
}
return true;
});
const mergeGroupByFieldContainsIf = (fieldName: string) => {
const mergeGroupByField = tools.cm.mergeGroupByField.value;
if (Array.isArray(mergeGroupByField) && mergeGroupByField.length > 0) {
const index = mergeGroupByField.findIndex((item) => item === fieldName);
if (index > -1) {
return true;
}
}
return false;
};
const sortByContainsIf = (fieldName: string) => {
if (tools.props.sortBy && tools.props.sortBy.length > 0) {
const findResult = tools.props.sortBy.findIndex((item) => {
if (item.startsWith('-') && item.substring(1) === fieldName) {
return true;
}
return item === fieldName;
});
return findResult > -1;
}
return false;
};
const tdClick = () => {
// 退
const cellSelected = tools.table.store.cellSelected;
if (cellSelected) {
if (tools.props.localMode && tools.table.store.inlineEditStatus === Constant.EDIT_STATUS.CELL && tools.props.selectMode === Constant.SELECT_MODE.CELL) {
if (cellSelected.colName !== props.col['name'] || cellSelected.row[rowKey] !== props.scope.row[rowKey]) {
tools.editFM.exitInlineEdit();
}
} else if (
tools.props.localMode &&
tools.table.store.inlineEditStatus === Constant.EDIT_STATUS.ROW &&
tools.props.selectMode === Constant.SELECT_MODE.CELL
) {
if (cellSelected.row[rowKey] !== props.scope.row[rowKey]) {
tools.editFM.exitInlineEdit();
}
}
}
if (tools.table.store.inlineEditStatus === Constant.EDIT_STATUS.NONE && tools.props.selectMode === Constant.SELECT_MODE.CELL) {
tools.table.store.cellSelected = {
row: toRaw(props.scope.row),
rowKey: props.scope.row[rowKey],
primaryKey: props.scope.row[tools.props.primaryKey],
colName: props.col['name'],
value: props.scope.row[props.col['name']],
};
}
};
onMounted(() => {
if (tdRef.value?.$el) {
tdWidth.value = tdRef.value.$el.clientWidth;
}
});
</script>
<style lang="css">
.sortNo_td {
padding: 0;
width: 50px;
min-width: 50px;
max-width: 50px;
}
</style>

2
io.sc.platform.core.frontend/src/platform/components/grid/GridAppendContent.vue → io.sc.platform.core.frontend/src/platform/components/grid/TdContent.vue

@ -1,5 +1,5 @@
<template>
<template v-if="typeof value === 'object' && value.componentType">
<template v-if="value && typeof value === 'object' && value.componentType">
<component :is="value.componentType" v-bind="value.attrs"></component>
</template>
<template v-else>

287
io.sc.platform.core.frontend/src/platform/components/grid/Top.vue

@ -0,0 +1,287 @@
<template>
<div class="col">
<template v-if="tools.table.advancedQueryStatus">
<!-- 高级查询 -->
<AdvancedQuery ref="formRef"></AdvancedQuery>
</template>
<template v-else>
<!-- 普通查询form -->
<w-form ref="formRef" v-bind="tools.props.queryFormAttrs" :fields="queryFormFieldsComputed" :cols-num="tools.props.queryFormColsNum"></w-form>
</template>
<!-- 标题与按钮 -->
<div
v-if="tools.props.title || buttons_.length > 0 || tools.props.configButton || tools.table.queryFormDisplayFields.length > 0"
class="flex flex-nowrap items-end"
:class="tools.table.queryFormDisplayFields.length > 0 ? 'pt-2.5' : ''"
>
<!-- 标题 -->
<div class="flex-none">{{ $t(tools.props.title ? tools.props.title : '') }}</div>
<!-- 按钮 -->
<div class="flex-1">
<w-toolbar ref="toolbarRef" v-bind="tools.props.toolbarConfigure" v-model="buttons_" :dense="denseToolbarComputed" :grid="tools.instance"></w-toolbar>
</div>
<!-- 最右侧用户配置按钮 -->
<div v-if="tools.props.configButton" class="flex-none pl-1">
<q-btn round dense :size="denseToolbarComputed ? '13px' : undefined" icon="manage_accounts" unelevated outline>
<q-popup-proxy v-model="tools.table.configStore.showConfigPanel">
<ConfigPanel :scope="props.scope" :grid="tools.instance"></ConfigPanel>
</q-popup-proxy>
</q-btn>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, inject, ref, onBeforeMount } from 'vue';
import { useQuasar } from 'quasar';
import { Tools, $t } from '@/platform';
import ConfigPanel from './extra/config/ConfigPanel.vue';
import AdvancedQuery from './extra/advanced-query/AdvancedQuery.vue';
import { Constant, GridTools } from './ts/index';
const $q = useQuasar();
const tools = <GridTools>inject('tools');
const formRef = ref();
const toolbarRef = ref();
const localeFlag = ref(false);
const buttons_ = ref(<any>[]);
const props = defineProps({
scope: {
//
type: Object,
default: () => {
return {};
},
},
});
const buttons = tools.bm.getButtonConfig();
const queryFormFieldsComputed = computed(() => {
if (localeFlag.value) {
return tools.table.queryFormDisplayFields;
}
return tools.table.queryFormDisplayFields;
});
const denseToolbarComputed = computed(() => {
if (tools.table.configStore.denseToolbar) {
return true;
} else if (tools.table.configStore.dense !== false) {
return true;
} else {
return false;
}
});
const screenCols = { xs: 1, sm: 2, md: 3, lg: 4, xl: 6 };
const queryFormColsNumComputed = computed(() => {
if (typeof tools.props.queryFormColsNum === 'number' && tools.props.queryFormColsNum > 0) {
return tools.props.queryFormColsNum;
} else if (typeof tools.props.queryFormColsNum === 'object') {
const screen = { ...screenCols, ...tools.props.queryFormColsNum };
return screen[$q.screen.name];
}
return screenCols[$q.screen.name];
});
// form
const handleQueryFormShowField = () => {
tools.table.queryFormDisplayFields = [];
if (tools.table.moreQueryStatus) {
tools.props.queryFormFields.forEach((item: any) => {
tools.table.queryFormDisplayFields.push(item);
item.showIf = () => {
return true;
};
});
} else {
//
const rowColsNum = queryFormColsNumComputed.value * (tools.props.queryFormRowNum || 1);
let currRowColsNum = 0;
tools.props.queryFormFields.forEach((item: any) => {
if (Tools.hasOwnProperty(item, 'colSpan')) {
currRowColsNum += item.colSpan;
} else {
currRowColsNum += 1;
}
if (currRowColsNum <= rowColsNum) {
tools.table.queryFormDisplayFields.push(item);
item.showIf = (form) => {
return true;
};
} else {
item.showIf = (form) => {
return false;
};
}
});
}
};
// toobar
const handleChildrenBtn = (arr, moreQueryShow) => {
const tempArr = <any>[];
for (let i = 0; i < arr.length; i++) {
const btn = arr[i];
if (typeof btn === 'string' && !buttons[btn]) {
throw new Error('[w-grid]`' + btn + '`' + ' toolbar action NOT exist.');
} else if (Array.isArray(btn) && btn.length > 0) {
const handleResult = handleChildrenBtn(btn, moreQueryShow);
if (handleResult && handleResult.length > 0) {
tempArr.push(handleResult);
}
} else if (typeof btn === 'string' && buttons[btn]) {
if (btn === tools.bm.query.name && i === 0) {
// QBtnDropdown
// ['query', 'reset']
tempArr.push(buttons[btn]);
tempArr.push(buttons[tools.bm.moreQuery.name]);
tempArr.push(buttons[tools.bm.advancedQuery.name]);
} else if (btn === tools.bm.query.name) {
tempArr.push([buttons[btn], buttons[tools.bm.moreQuery.name]]);
} else {
tempArr.push(buttons[btn]);
}
} else if (typeof btn === 'object' && btn.extend && buttons[btn.extend]) {
//
let overrideClick = false;
if (btn.click) {
overrideClick = true;
}
tempArr.push({ ...buttons[btn.extend], ...btn, _click: buttons[btn.extend].click, overrideClick });
} else {
tempArr.push(btn);
}
}
return tempArr;
};
const handleToolbarActions = () => {
buttons_.value = [];
//
let moreQueryShow = false;
const rowColsNum = queryFormColsNumComputed.value * (tools.props.queryFormRowNum || 1);
let currRowColsNum = 0;
let showQueryFormFieldNum = 0;
tools.props.queryFormFields.forEach((item: any) => {
if (Tools.hasOwnProperty(item, 'colSpan')) {
currRowColsNum += item.colSpan;
} else {
currRowColsNum += 1;
}
if (currRowColsNum <= rowColsNum) {
showQueryFormFieldNum += 1;
}
});
if (showQueryFormFieldNum < tools.props.queryFormFields.length) {
moreQueryShow = true;
}
tools.props.toolbarActions.forEach((btn: any, index) => {
if (typeof btn === 'string' && !buttons[btn]) {
throw new Error('[w-grid]`' + btn + '`' + ' toolbar action NOT exist.');
} else if (typeof btn === 'string' && buttons[btn]) {
if (btn === tools.bm.query.name) {
const tmpBtns = [buttons[btn]];
if (moreQueryShow) {
tmpBtns.push(buttons[tools.bm.moreQuery.name]);
}
if (tools.props.advancedQuery) {
tmpBtns.push(buttons[tools.bm.advancedQuery.name]);
}
if (tmpBtns.length === 1) {
buttons_.value.push(...tmpBtns);
} else {
buttons_.value.push(tmpBtns);
}
} else {
buttons_.value.push(buttons[btn]);
}
} else if (Array.isArray(btn) && btn.length > 0) {
buttons_.value.push(handleChildrenBtn(btn, moreQueryShow));
} else if (typeof btn === 'object' && btn.extend && buttons[btn.extend]) {
//
let overrideClick = false;
if (btn.click) {
overrideClick = true;
}
buttons_.value.push({ ...buttons[btn.extend], ...btn, _click: buttons[btn.extend].click, overrideClick });
} else {
buttons_.value.push(btn);
}
});
if (buttons_.value.length > 0 && buttons_.value[buttons_.value.length - 1] !== tools.bm.separator.name && tools.props.configButton) {
buttons_.value.push(buttons[tools.bm.separator.name]);
}
setLocaleFlag();
};
const setLocaleFlag = () => {
localeFlag.value = !localeFlag.value;
};
const resetLabel = () => {
Object.keys(buttons).forEach((btn) => {
if (typeof buttons[btn] === 'object' && buttons[btn].labelI18nKey) {
buttons[btn].label = $t(buttons[btn].labelI18nKey);
}
});
};
const buttonClick = (arr) => {
arr.forEach((button) => {
if (typeof button === 'object' && !Tools.isEmpty(button.click) && tools.props.dbClickOperation === button.name) {
toolbarRef.value.buttonClick(button);
} else if (Array.isArray(button) && button.length > 0) {
buttonClick(button);
}
});
};
const dbClickOperation = (row) => {
if (!Tools.isEmpty(row) && tools.props.dbClickOperation === tools.bm.expand.name) {
row[Constant.FIELD_NAMES.EXPAND] = Tools.isEmpty(row[Constant.FIELD_NAMES.EXPAND]) ? true : !row[Constant.FIELD_NAMES.EXPAND];
} else {
if (!buttons[tools.props.dbClickOperation] && checkConfigNotContains(buttons_.value)) {
throw new Error('[w-grid]`' + tools.props.dbClickOperation + '`' + ' toolbar action NOT exist.');
} else if (tools.props.dbClickOperation !== tools.bm.separator.name && buttons[tools.props.dbClickOperation] && checkConfigNotContains(buttons_.value)) {
// dbClickOperation
toolbarRef.value.buttonClick(buttons[tools.props.dbClickOperation]);
} else {
buttonClick(buttons_.value);
}
}
};
//
const checkConfigNotContains = (arr) => {
let flag = true;
for (let i = 0; i < arr.length; i++) {
if (typeof arr[i] === 'object' && tools.props.dbClickOperation === arr[i]['name']) {
flag = false;
break;
} else if (Array.isArray(arr[i]) && arr[i].length > 0) {
const tempFlag = checkConfigNotContains(arr[i]);
flag = tempFlag;
}
}
return flag;
};
const getQueryForm = () => {
return formRef.value;
};
onBeforeMount(() => {
handleToolbarActions();
});
defineExpose({
setLocaleFlag,
handleQueryFormShowField,
handleToolbarActions,
resetLabel,
dbClickOperation,
getQueryForm,
});
</script>
<style lang="css"></style>

117
io.sc.platform.core.frontend/src/platform/components/grid/Tr.vue

@ -0,0 +1,117 @@
<template>
<q-tr
ref="trRef"
:props="props.scope"
:no-hover="trNoHoverComputed"
:class="{ selected: trSelectedClassComputed }"
:draggable="draggableComputed"
@click.stop="tools.em.rowClick($event, props.scope.row, props.scope.rowIndex)"
@dblclick.stop="tools.em.rowDbClick($event, scope.row, scope.rowIndex)"
@dragstart="draggableComputed ? tools.dndFM.onDragStart($event, scope) : () => {}"
@dragleave="draggableComputed ? tools.dndFM.onDragLeave($event) : () => {}"
@dragover="draggableComputed ? tools.dndFM.onDragOver($event, scope, trMiddleHeightComputed) : () => {}"
@drop="draggableComputed ? tools.dndFM.onDrop($event, scope, trMiddleHeightComputed) : () => {}"
@dragend="draggableComputed ? tools.dndFM.onDragEnd($event, scope) : () => {}"
>
<q-td v-if="showCheckboxComputed" class="text-center checkbox_td">
<!-- checkbo选项 -->
<q-checkbox
v-model="rowDataComputed[tickedField]"
flat
:dense="tools.table.configStore.dense || tools.table.configStore.denseBody || false"
@update:model-value="tools.em.updateTicked($event, scope.row)"
/>
</q-td>
<template v-for="(col, index) in props.scope.cols" :key="col.name">
<template v-if="index === 0">
<Td :scope="props.scope" :col="col" :level="props.level"></Td>
</template>
<Td v-else :scope="props.scope" :col="col" :level="props.level"></Td>
</template>
</q-tr>
<template v-if="showChildrenTrComputed">
<!-- 处理树表格的孩子行 -->
<template v-for="(child, childIndex) in props.scope.row.children" :key="child[Constant.FIELD_NAMES.ROW_KEY]">
<Tr
v-if="props.scope.row[Constant.FIELD_NAMES.EXPAND]"
:scope="GridTools.buildChildrenScope(props.scope, child, props.scope.pageIndex + childIndex, selectedField, tickedField)"
:level="props.level + 1"
></Tr>
</template>
</template>
<InlineEditToolbar :row="props.scope.row"></InlineEditToolbar>
</template>
<script setup lang="ts">
import { computed, inject, ref } from 'vue';
import { Tools } from '@/platform';
import { Constant, GridTools } from './ts/index';
import Td from './Td.vue';
import InlineEditToolbar from './extra/inline-edit/InlineEditToolbar.vue';
const tools = <GridTools>inject('tools');
const trRef = ref();
const props = defineProps({
level: { type: Number, default: 0 },
scope: {
type: Object,
default: () => {
return {};
},
},
});
const selectedField = tools.props.selectedField;
const tickedField = tools.props.tickedField;
const selectMode = tools.props.selectMode;
const dndMode = tools.props.dndMode;
// checkbox
const showCheckboxComputed = computed(() => {
return tools.table.configStore.useCheckboxSelection && tools.props.selectMode !== Constant.SELECT_MODE.NONE && !tools.props.tree;
});
//
const rowDataComputed = computed(() => {
return tools.dataFM.getRow(tools.table.rows, props.scope.row[Constant.FIELD_NAMES.ROW_KEY], false);
});
//
const showChildrenTrComputed = computed(() => {
return tools.props.tree && props.scope.row?.children;
});
// tr
const trNoHoverComputed = computed(() => {
if (selectMode === Constant.SELECT_MODE.ROW) {
return false;
}
return true;
});
// tr
const trSelectedClassComputed = computed(() => {
return props.scope.row[selectedField] && selectMode === Constant.SELECT_MODE.ROW;
});
//
const draggableComputed = computed(() => {
return dndMode && !Tools.isEmpty(Constant.DND_MODE.getAll()[dndMode]) && tools.table.store.inlineEditStatus === Constant.EDIT_STATUS.NONE;
});
//
const trMiddleHeightComputed = computed(() => {
if (trRef?.value) {
return trRef.value.$el.offsetHeight / 2;
} else {
return (tools.table.configStore.dense || tools.table.configStore.denseBody ? 24 : 48) / 2;
}
});
</script>
<style lang="css">
.checkbox_td {
padding: 0 !important;
width: 50px;
min-width: 50px;
max-width: 50px;
}
</style>

269
io.sc.platform.core.frontend/src/platform/components/grid/TreeGridFirstTdContent.vue

@ -0,0 +1,269 @@
<template>
<div class="flex flex-nowrap items-center h-full w-full treeGridFirstTd" style="flex-wrap: nowrap">
<!--层级占位符-->
<span :style="`width:${24 * props.level}px;`"></span>
<!--展开按钮-->
<q-btn
v-if="expandableComputed"
flat
dense
padding="0px 0px"
:icon="props.row.expand ? 'bi-dash' : 'bi-plus'"
:loading="lazyloading"
@click.stop="expandFun"
@dblclick.stop="() => {}"
>
<template #loading>
<q-spinner-gears />
</template>
</q-btn>
<!--展开按钮占位符-->
<span v-else style="width: 24px"></span>
<!--选择框-->
<q-checkbox
v-if="tools.table.configStore.useCheckboxSelection"
v-model="rowDataComputed[tickedField]"
flat
dense
@dblclick.stop="() => {}"
@update:model-value="selectedFun(rowDataComputed[tickedField], $event)"
/>
<!-- 图标与显示内容区域 -->
<div class="treeGridIconAndName" @dragover="draggableComputed ? tools.dndFM.iconAndNameDragOver($event, props.row) : () => {}">
<q-icon v-if="typeof iconComputed === 'string'" :name="iconComputed" size="20px" class="px-1 treeGridIconAndName"></q-icon>
<q-icon v-else-if="typeof iconComputed === 'object'" size="20px" v-bind="iconComputed" class="px-1 treeGridIconAndName"></q-icon>
<span v-else class="px-1 treeGridIconAndName"></span>
<template v-if="tools.table.store.inlineEditStatus !== Constant.EDIT_STATUS.NONE && showInlineEditComponentComputed">
<InlineEditComponent :col="props.col" :row="props.row"></InlineEditComponent>
</template>
<template v-else>
<TdContent :value="props.value" class="treeGridIconAndName"></TdContent>
</template>
</div>
</div>
</template>
<script setup lang="ts">
import { inject, computed, ref, watch } from 'vue';
import { Tools, NotifyManager } from '@/platform';
import InlineEditComponent from './extra/inline-edit/InlineEditComponent.vue';
import TdContent from './TdContent.vue';
import { DragAndDrop } from './ts/function/DragAndDrop';
import { Constant, GridTools } from './ts/index';
const tools = <GridTools>inject('tools');
const lazyloading = ref(false);
const props = defineProps({
level: { type: Number, default: 0 },
row: {
type: Object,
default: () => {
return {};
},
},
col: {
type: Object,
default: () => {
return {};
},
},
value: {
type: [Object, String, Number],
default: () => {
return '';
},
},
});
const selectedField = tools.props.selectedField;
const tickedField = tools.props.tickedField;
const rowKeyField = Constant.FIELD_NAMES.ROW_KEY;
const expandField = Constant.FIELD_NAMES.EXPAND;
const lazyloadNoChildrenField = Constant.FIELD_NAMES.LAZYLOAD_NO_CHILDREN;
const style = {
icon: {
defaultTop: {
//
name: 'bi-folder-fill',
color: 'amber',
},
defaultChild: {
//
name: 'note',
color: '',
},
},
};
const expandableComputed = computed(() => {
if (tools.props.treeLazyLoad && !props.row[lazyloadNoChildrenField]) {
return true;
} else if (props.row.children && props.row.children.length > 0) {
return true;
}
return false;
});
//
const rowDataComputed = computed(() => {
return tools.dataFM.getRow(tools.table.rows, props.row[rowKeyField], false);
});
const draggableComputed = computed(() => {
return (
tools.props.dndMode && !Tools.isEmpty(Constant.DND_MODE.getAll()[tools.props.dndMode]) && tools.table.store.inlineEditStatus === Constant.EDIT_STATUS.NONE
);
});
const iconComputed = computed(() => {
if (!Tools.isEmpty(props.row['_dragIcon_'])) {
return {
name: props.row['_dragIcon_'],
color: DragAndDrop.DRAG_ICON.color,
};
} else if (tools.props.treeIcon) {
return tools.props.treeIcon(props.row);
} else {
if (tools.props.treeLazyLoad && !props.row[lazyloadNoChildrenField]) {
return {
name: style.icon.defaultTop.name,
color: style.icon.defaultTop.color,
};
} else if (props.row.children && props.row.children.length > 0) {
return {
name: style.icon.defaultTop.name,
color: style.icon.defaultTop.color,
};
} else {
return {
name: style.icon.defaultChild.name,
color: style.icon.defaultChild.color,
};
}
}
});
const showInlineEditComponentComputed = computed(() => {
return tools.editFM.isShowInlineEditor(props.col, props.row[rowKeyField]);
});
// /
const expandFun = async () => {
if (tools.table.store.inlineEditStatus === Constant.EDIT_STATUS.NONE) {
if (tools.props.treeLazyLoad && (!props.row.children || props.row.children.length === 0)) {
lazyLoadExpandHandle(props.row);
} else {
rowDataComputed.value[expandField] = !props.row[expandField];
}
} else {
NotifyManager.info('请先保存或取消编辑状态');
}
};
const lazyLoadExpandHandle = async (row: any) => {
lazyloading.value = true;
const childrenData = await tools.reqApiFM.treeFetchDataByForeignKey(row[tools.props.primaryKey]);
if (childrenData.length > 0) {
tools.apiFM.localMode.addLocalData(childrenData);
} else {
rowDataComputed.value[lazyloadNoChildrenField] = true;
}
rowDataComputed.value[expandField] = true;
lazyloading.value = false;
};
// checkbox
const selectedFun = (value, event) => {
const row = rowDataComputed.value;
if (tools.table.store.inlineEditStatus === Constant.EDIT_STATUS.NONE) {
if (value) {
row[tickedField] = true;
} else {
row[tickedField] = false;
}
selectedChildren(row, value);
selectedParent(row, value);
if (tools.props.onUpdateTicked) {
tools.props.onUpdateTicked(event, row);
}
} else {
row[tickedField] = !row[tickedField];
}
};
//
const selectedChildren = (row, value) => {
if (row.children && row.children.length > 0) {
for (let child of row.children) {
child[tickedField] = value;
selectedChildren(child, value);
}
}
};
//
const selectedParent = (row, value) => {
if (row.parent) {
const parent = tools.dataFM.getRow(tools.table.rows, row.parent, true);
if (parent) {
if (value && childrenSelectedStatus(parent).allSelected) {
parent[tickedField] = true;
} else if (value) {
parent[tickedField] = null;
} else {
if (childrenSelectedStatus(parent).partSelected) {
parent[tickedField] = null;
} else {
parent[tickedField] = false;
}
}
selectedParent(parent, value);
}
}
};
//
const childrenSelectedStatus = (row) => {
let result = {
allSelected: true,
partSelected: false,
};
if (row.children && row.children.length > 0) {
for (let i = 0; i < row.children.length; i++) {
if (!row.children[i][tickedField]) {
result.allSelected = false;
} else {
result.partSelected = true;
}
const temp = childrenSelectedStatus(row.children[i]);
if (!temp.allSelected) {
result.allSelected = false;
}
if (temp.partSelected) {
result.partSelected = true;
}
}
}
return result;
};
watch(
() => tools.dndFM.iconAndNameDwellTime.value,
(newVal, oldVal) => {
if (
newVal >= 3 &&
tools.dndFM.dwellRow &&
!tools.dndFM.dwellRow[Constant.FIELD_NAMES.EXPAND] &&
tools.dndFM.dwellRow[Constant.FIELD_NAMES.ROW_KEY] === props.row[Constant.FIELD_NAMES.ROW_KEY]
) {
if (tools.props.treeLazyLoad && (!props.row.children || props.row.children.length === 0)) {
lazyLoadExpandHandle(tools.dndFM.dwellRow);
} else {
tools.apiFM.operator.expand(tools.dndFM.dwellRow);
}
}
},
);
</script>
<style lang="css"></style>

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

@ -1,907 +0,0 @@
<template>
<q-tr
ref="trRef"
:no-hover="props.grid.props.selectMode === selectMode.row ? false : true"
:class="row[table.selectedField] && props.grid.props.selectMode === selectMode.row ? 'selected' : ''"
:draggable="draggableComputed"
@click.stop.prevent="click($event, row, props.rowIndex)"
@dblclick.stop.prevent="dbClick($event, row, props.rowIndex)"
@dragleave="draggableComputed ? onDragLeave($event, row) : () => {}"
@dragover="draggableComputed ? onDragOver($event, row) : () => {}"
@drop="draggableComputed ? onDrop($event, row) : () => {}"
@dragstart="draggableComputed ? onDragStart($event, row) : () => {}"
>
<q-td
:ref="(el) => setTdRef(el, cols[0])"
:class="props.grid.props.selectMode === selectMode.cell ? firstTdClassComputed : 'nowrap text-nowrap'"
:style="cols[0].type && table.bodyEditStatus !== 'none' ? 'width: ' + (tdWidth[cols[0].name] - 24) + 'px;' : ''"
@click="
() => {
// 退
if (props.grid.props.localMode && table.bodyEditStatus === editStatus.cell && props.grid.props.selectMode === selectMode.cell) {
if (table['cellSelected']['colName'] !== cols[0]['name'] || table['cellSelected']['row'][props.rowKey] !== props.row[props.rowKey]) {
table.bodyEditStatus = 'none';
}
} else if (props.grid.props.localMode && table.bodyEditStatus === editStatus.row && props.grid.props.selectMode === selectMode.cell) {
if (table['cellSelected']['row'][props.rowKey] !== props.row[props.rowKey]) {
table.bodyEditStatus = 'none';
}
}
if (table.bodyEditStatus === 'none' && props.grid.props.selectMode === selectMode.cell) {
table['cellSelected'] = {
row: toRaw(props.row),
rowKey: props.row[props.rowKey],
primaryKey: props.row[props.grid.props.primaryKey],
colName: cols[0]['name'],
value: props.row[cols[0]['name']],
};
}
}
"
>
<div ref="tdDivRef" class="flex flex-nowrap items-center h-full w-full" style="flex-wrap: nowrap">
<!--层级占位符-->
<span :style="`width:${24 * props.level}px;`"></span>
<!--展开按钮-->
<q-btn
v-if="row.children && row.children.length > 0"
flat
dense
padding="0px 0px"
:icon="row.expand ? 'bi-dash' : 'bi-plus'"
@click.stop="expandFun(row)"
@dblclick.stop="() => {}"
/>
<!--展开按钮占位符-->
<span v-else style="width: 24px"></span>
<!--选择框-->
<q-checkbox
v-if="table.checkboxSelection"
v-model="props.getRow(table.rows, row[props.rowKey], false)[table.tickedField]"
flat
dense
@dblclick.stop="() => {}"
@update:model-value="selectedFun(row[table.tickedField], row, $event)"
/>
<!--图标-->
<q-icon v-if="typeof iconComputed === 'string'" :name="iconComputed" size="20px" class="px-1"></q-icon>
<q-icon v-else-if="typeof iconComputed === 'object'" size="20px" v-bind="iconComputed" class="px-1"></q-icon>
<span v-else class="px-1"></span>
<template v-if="cols[0]">
<template
v-if="
!Tools.isEmpty(cols[0]['type']) &&
((isSelectedRowComputed && table.bodyEditStatus === 'rowEdit') ||
table.bodyEditStatus === 'rowsEdit' ||
(isSelectedCell(cols[0]) && table.bodyEditStatus === 'cellEdit'))
"
>
<template v-if="props.grid.props.localMode && table.bodyEditStatus !== editStatus.rows">
<component
:is="cols[0]['type']"
:ref="(el) => setComponentRef(el, props.row, cols[0])"
v-bind="cols[0]['attrs']"
v-model="props.getRow(table.rows, props.row[props.rowKey], false)[cols[0]['name']]"
bg-color="light-green-1"
@blur="
() => {
if (props.grid.props.selectMode === selectMode.cell && table.bodyEditStatus === editStatus.cell) {
// 退
table.bodyEditStatus = editStatus.none;
table['cellSelected'] = {};
}
}
"
></component>
</template>
<template v-else>
<component
:is="cols[0]['type']"
:ref="(el) => setComponentRef(el, props.row, cols[0])"
v-bind="cols[0]['attrs']"
v-model="props.getRow(table.rows, props.row[props.rowKey], false)['_rowOldValue'][cols[0]['name']]"
bg-color="light-green-1"
></component>
</template>
</template>
<template v-else>
<template v-if="cols[0].value && typeof cols[0].value === 'object' && cols[0].value.componentType">
<component :is="cols[0].value.componentType" v-bind="cols[0].value.attrs"></component>
</template>
<template v-else>
<span v-dompurify-html="cols[0].value || ''"></span>
</template>
</template>
</template>
</div>
</q-td>
<template v-for="(col, index) in cols" :key="col.name">
<q-td
v-if="index > 0"
:ref="(el) => setTdRef(el, col)"
:class="props.grid.props.selectMode === selectMode.cell ? tdClassComputed(col) : col.__thClass + ' ' + col.classes"
:title="getTitle(col)"
:style="col.style + (col.type && table.bodyEditStatus !== 'none' ? ';width: ' + (tdWidth[col.name] - 24) + 'px;' : '')"
@click="
() => {
// 退
if (props.grid.props.localMode && table.bodyEditStatus === editStatus.cell && props.grid.props.selectMode === selectMode.cell) {
if (table['cellSelected']['colName'] !== col['name'] || table['cellSelected']['row'][props.rowKey] !== props.row[props.rowKey]) {
table.bodyEditStatus = 'none';
}
} else if (props.grid.props.localMode && table.bodyEditStatus === editStatus.row && props.grid.props.selectMode === selectMode.cell) {
if (table['cellSelected']['row'][props.rowKey] !== props.row[props.rowKey]) {
table.bodyEditStatus = 'none';
}
}
if (table.bodyEditStatus === 'none' && props.grid.props.selectMode === selectMode.cell) {
table['cellSelected'] = {
row: toRaw(props.row),
rowKey: props.row[props.rowKey],
primaryKey: props.row[props.grid.props.primaryKey],
colName: col['name'],
value: props.row[col['name']],
};
}
}
"
>
<template
v-if="
!Tools.isEmpty(col.type) &&
((isSelectedRowComputed && table.bodyEditStatus === 'rowEdit') ||
table.bodyEditStatus === 'rowsEdit' ||
(isSelectedCell(col) && table.bodyEditStatus === 'cellEdit'))
"
>
<template v-if="props.grid.props.localMode && table.bodyEditStatus !== editStatus.rows">
<component
:is="col.type"
:ref="(el) => setComponentRef(el, props.row, col)"
v-bind="componentAttrs(col)"
v-model="props.getRow(table.rows, props.row[props.rowKey], false)[col.name]"
bg-color="light-green-1"
></component>
</template>
<template v-else>
<component
:is="col.type"
:ref="(el) => setComponentRef(el, props.row, col)"
v-bind="componentAttrs(col)"
v-model="props.getRow(table.rows, props.row[props.rowKey], false)['_rowOldValue'][col.name]"
bg-color="light-green-1"
></component>
</template>
</template>
<template v-else>
<template v-if="col.value && typeof col.value === 'object' && col.value.componentType && col.value.bindModelValue">
<component
:is="col.value.componentType"
v-bind="col.value.attrs"
v-model="props.getRow(table.rows, row[props.rowKey], false)[col.name]"
></component>
</template>
<template v-else-if="col.value && typeof col.value === 'object' && col.value.componentType">
<component :is="col.value.componentType" v-bind="col.value.attrs"></component>
</template>
<template v-else>
<span v-dompurify-html="!Tools.isEmpty(col.value) ? col.value : ''"></span>
</template>
</template>
</q-td>
</template>
</q-tr>
<GridEditToolbar
:grid="props.grid"
:url="props.url"
:row-key-name="props.rowKey"
:row="props.row"
:get-row="props.getRow"
:get-row-component-refs="props.getRowComponentRefs"
:set-old-value="props.setOldValue"
:no-data-tr-colspan="props.noDataTrColspan"
></GridEditToolbar>
<template v-for="(child, index) in row.children" :key="child[rowKey]">
<TreeGridRow
v-if="row.expand"
:columns-map="props.columnsMap"
:row="child"
:cols="childColsHandler(child)"
:level="props.level + 1"
:row-key="props.rowKey"
:grid="props.grid"
:grid-row-click="props.gridRowClick"
:grid-row-db-click="gridRowDbClick"
:after-drag-and-drop="props.afterDragAndDrop"
:get-row="props.getRow"
:url="props.url"
:get-row-component-refs="props.getRowComponentRefs"
:set-old-value="props.setOldValue"
:no-data-tr-colspan="props.noDataTrColspan"
:updates="props.updates"
:row-index="index"
></TreeGridRow>
</template>
</template>
<script setup lang="ts">
import { ref, computed, inject, toRaw, reactive, onMounted } from 'vue';
import { Tools, NotifyManager } from '@/platform';
import GridEditToolbar from './GridEditToolbar.vue';
import { dndImage, dndMode, selectMode, editStatus } from './ts/grid';
const trRef = ref();
const tdDivRef = ref();
const componentOneRef = ref();
const componentRef = ref({});
const tdRef = ref({});
const tdWidth = ref({});
const props = defineProps({
level: { type: Number, default: 0 },
columnsMap: {
type: Map,
default: () => {
return new Map();
},
},
row: {
type: Object,
default: () => {
return {};
},
},
cols: {
type: Array,
default: () => {
return [];
},
},
rowKey: { type: String, default: '_rowKey_' },
rowIndex: { type: Number, default: undefined },
grid: {
type: Object,
default: () => {
return {};
},
},
getRow: {
type: Function,
default: () => {},
},
gridRowClick: {
type: Function,
default: () => {},
},
gridRowDbClick: {
type: Function,
default: () => {},
},
afterDragAndDrop: {
type: Function,
default: () => {},
},
url: {
// url
type: Object,
default: () => {
return {};
},
},
getRowComponentRefs: {
type: Function,
default: () => {},
},
setOldValue: {
type: Function,
default: () => {},
},
noDataTrColspan: {
type: Number,
default: () => {
return 0;
},
},
updates: {
type: Function,
default: () => {},
},
});
const table = inject('table');
const style = {
icon: {
defaultTop: {
//
name: 'bi-folder-fill',
color: 'amber',
},
defaultChild: {
//
name: 'note',
color: '',
},
drag: {
//
name: 'bi-folder-symlink-fill',
color: 'amber',
},
},
dragLine: {
// 线
width: '2px',
style: 'dashed',
color: 'orange',
},
};
const getTitle = (col) => {
if (table['columns'] && table['columns'].length > 0) {
const column = table['columns'].find((item) => item['name'] === col.name);
if (column && column['title'] && typeof column['title'] === 'function') {
return column.title({ grid: props.grid, row: toRaw(props.row), value: props.row[col.name] });
} else if (column && column['title']) {
return column['title'];
}
}
if (col.classes?.indexOf('truncate') > -1 && !Tools.isEmpty(props.row[col.name]) && typeof props.row[col.name] !== 'object') {
return props.row[col.name];
}
return '';
};
const firstTdClassComputed = computed(() => {
const tdClass = <any>['nowrap', 'text-nowrap'];
if (props.grid.props.selectMode === selectMode.cell) {
tdClass.push('cellHover');
}
if (table && table['cellSelected'] && Tools.hasOwnProperty(table['cellSelected'], 'colName')) {
if (table['cellSelected']['colName'] === props.cols[0]['name'] && table['cellSelected']['rowKey'] === props.row[props.rowKey]) {
tdClass.push('cellSelected');
}
}
return tdClass;
});
const tdClassComputed = computed(() => {
return (col) => {
let tdClass = col.__thClass + ' ' + col.classes;
if (props.grid.props.selectMode === selectMode.cell) {
tdClass += ' cellHover';
}
if (table && table['cellSelected'] && Tools.hasOwnProperty(table['cellSelected'], 'colName')) {
if (table['cellSelected']['colName'] === col['name'] && table['cellSelected']['rowKey'] === props.row[props.rowKey]) {
tdClass += ' cellSelected';
}
}
return tdClass;
};
});
const draggableComputed = computed(() => {
if (
props.grid.props.dndMode &&
typeof props.grid.props.dndMode === 'string' &&
!Tools.isEmpty(dndMode[props.grid.props.dndMode]) &&
table.bodyEditStatus === editStatus.none
) {
return true;
}
return false;
});
const isSelectedRowComputed = computed(() => {
const selected = props.grid.getSelectedRow();
if (!Tools.isEmpty(selected)) {
return props.row[props.rowKey] === props.grid.getSelectedRow()[props.rowKey];
}
return false;
});
const isSelectedCell = (col) => {
const selected = props.grid.getSelectedCell();
if (selected?.colName && props.row[props.rowKey] === selected['row'][props.rowKey] && col['name'] === selected.colName) {
return true;
}
return false;
};
const iconComputed = computed(() => {
if (!Tools.isEmpty(props.row['_dragIcon_'])) {
return {
name: props.row['_dragIcon_'],
color: style.icon.drag.color,
};
} else if (props.grid.props.treeIcon) {
return props.grid.props.treeIcon(props.row);
} else {
if (props.row.children && props.row.children.length > 0) {
return {
name: style.icon.defaultTop.name,
color: style.icon.defaultTop.color,
};
} else {
return {
name: style.icon.defaultChild.name,
color: style.icon.defaultChild.color,
};
}
}
});
const componentAttrs = (col) => {
if (col.attrs) {
return col.attrs;
} else if (props.grid.props.editor?.form?.fields) {
const field = props.grid.props.editor.form.fields.find((item) => item['name'] === col.name);
if (field) {
return { ...field, label: undefined };
}
}
return undefined;
};
// /
const expandFun = (row) => {
if (table.bodyEditStatus === 'none') {
row.expand = !row.expand;
} else {
NotifyManager.info('请先保存或取消编辑状态');
}
};
// checkbox
const selectedFun = (value, row, event) => {
if (table.bodyEditStatus === 'none') {
if (value) {
props.getRow(table.rows, row[props.rowKey], false)[table.tickedField] = true;
} else {
props.getRow(table.rows, row[props.rowKey], false)[table.tickedField] = false;
}
selectedChildren(row, value);
selectedParent(row, value);
if (props.grid.props.onUpdateTicked) {
props.grid.props.onUpdateTicked(event, row);
}
} else {
props.getRow(table.rows, row[props.rowKey], false)[table.tickedField] = !props.getRow(table.rows, row[props.rowKey], false)[table.tickedField];
}
};
//
const selectedChildren = (row, value) => {
if (row.children && row.children.length > 0) {
for (let child of row.children) {
child[table.tickedField] = value;
selectedChildren(child, value);
}
}
};
//
const selectedParent = (row, value) => {
if (row.parent) {
const parent = props.getRow(table.rows, row.parent, true);
if (parent) {
if (value && childrenSelectedStatus(parent).allSelected) {
parent[table.tickedField] = true;
// selectedPush(parent);
} else if (value) {
parent[table.tickedField] = null;
} else {
// selectedRemove(parent);
if (childrenSelectedStatus(parent).partSelected) {
parent[table.tickedField] = null;
} else {
parent[table.tickedField] = false;
}
}
selectedParent(parent, value);
}
}
};
//
const childrenSelectedStatus = (row) => {
let result = {
allSelected: true,
partSelected: false,
};
if (row.children && row.children.length > 0) {
for (let i = 0; i < row.children.length; i++) {
if (!row.children[i][table.tickedField]) {
result.allSelected = false;
} else {
result.partSelected = true;
}
const temp = childrenSelectedStatus(row.children[i]);
if (!temp.allSelected) {
result.allSelected = false;
}
if (temp.partSelected) {
result.partSelected = true;
}
}
}
return result;
};
// cols
const childColsHandler = (child) => {
const cols = <any>[];
props.cols.forEach((col) => {
if (props.columnsMap.get(col.name) && props.columnsMap.get(col.name).format) {
let value = child[col.name];
try {
value = props.columnsMap.get(col.name).format(child[col.name], child);
} catch (error) {
console.error('format error!');
}
cols.push({ ...col, value: value });
} else {
cols.push({ ...col, value: child[col.name] });
}
});
return cols;
};
document.addEventListener('dragstart', function (e) {
if (tdDivRef?.value) {
tdDivRef.value.classList.add('gridTdDiv');
}
});
document.addEventListener('drop', function (e) {
if (tdDivRef?.value) {
tdDivRef.value.classList.remove('gridTdDiv');
}
});
//
const onDragStart = (e, dataRow) => {
const img = new Image();
img.src = dndImage;
e.dataTransfer.setDragImage(img, 0, 0);
const selecteds = props.grid.getSelectedRows();
if (
selecteds.length > 0 &&
selecteds.findIndex((item) => {
return item[props.rowKey] === dataRow[props.rowKey];
}) > -1
) {
table.dragRecords = selecteds;
} else {
props.grid.clearSelected();
//
dataRow[table.selectedField] = true;
table.dragRecords = [dataRow];
}
e.dataTransfer.effectAllowed = 'move';
};
const addDragTopStyle = (e) => {
if (e.target?.parentNode?.children && e.target.nodeName === 'TD') {
for (let i = 0; i < e.target.parentNode.children.length; i++) {
e.target.parentNode.children[i].style.borderTopWidth = style.dragLine.width;
e.target.parentNode.children[i].style.borderTopStyle = style.dragLine.style;
e.target.parentNode.children[i].style.borderTopColor = style.dragLine.color;
}
}
};
const removeDragTopStyle = (e) => {
if (e.target?.parentNode?.children && e.target.nodeName === 'TD') {
for (let i = 0; i < e.target.parentNode.children.length; i++) {
e.target.parentNode.children[i].style.borderTopWidth = '';
e.target.parentNode.children[i].style.borderTopStyle = '';
e.target.parentNode.children[i].style.borderTopColor = '';
}
}
};
const addDragBottomStyle = (e) => {
if (e.target?.parentNode?.children && e.target.nodeName === 'TD') {
for (let i = 0; i < e.target.parentNode.children.length; i++) {
e.target.parentNode.children[i].style.borderBottomWidth = style.dragLine.width;
e.target.parentNode.children[i].style.borderBottomStyle = style.dragLine.style;
e.target.parentNode.children[i].style.borderBottomColor = style.dragLine.color;
}
}
};
const removeDragBottomStyle = (e) => {
if (e.target?.parentNode?.children && e.target.nodeName === 'TD') {
for (let i = 0; i < e.target.parentNode.children.length; i++) {
e.target.parentNode.children[i].style.borderBottomWidth = '';
e.target.parentNode.children[i].style.borderBottomStyle = '';
e.target.parentNode.children[i].style.borderBottomColor = '';
}
}
};
// icon
const setDragIcon = (arr, key: string = '') => {
for (let i = 0; i < arr.length; i++) {
if (arr[i].children && arr[i].children.length > 0) {
if (!Tools.isEmpty(key) && arr[i][props.grid.props.primaryKey] === key) {
if (Tools.isEmpty(arr[i]['_dragIcon_']) || arr[i]['_dragIcon_'] !== style.icon.drag.name) {
arr[i]['_dragIcon_'] = style.icon.drag.name;
}
} else {
arr[i]['_dragIcon_'] = '';
}
setDragIcon(arr[i].children, key);
}
}
};
/**
* 判断是否可以拖拽
* @param dragRecords 拖拽的记录集
* @param targetParentKey 当前鼠标所在的目标行的父节点key
*/
const canDrag = (dragRecords, targetParentKey) => {
if (Tools.isEmpty(targetParentKey)) {
return true;
}
//
let result = true;
let parentKey = targetParentKey;
while (!Tools.isEmpty(parentKey)) {
if (
dragRecords.findIndex((item) => {
return item[props.grid.props.primaryKey] === parentKey;
}) > -1
) {
result = false;
parentKey = null;
} else {
parentKey = props.getRow(table.rows, parentKey, true)[props.grid.props.foreignKey];
}
}
return result;
};
//
const gridTrMiddleHeightComputed = computed(() => {
if (trRef?.value) {
return trRef.value.$el.offsetHeight / 2;
} else {
return (table.dense || table.denseBody ? 24 : 48) / 2;
}
});
//
const dragRecordsContains = (dataRow) => {
return (
table.dragRecords.findIndex((item) => {
return item[props.rowKey] === dataRow[props.rowKey];
}) > -1
);
};
//
const onDragLeave = (e, dataRow) => {
removeDragTopStyle(e);
removeDragBottomStyle(e);
if (tdDivRef?.value) {
tdDivRef.value.classList.remove('gridTdDiv');
}
};
/**
* 拖拽过程触发
* @param e 拖拽的html元素
* @param dataRow 当前鼠标所在的目标行数据
*/
const onDragOver = (e, dataRow) => {
e.preventDefault();
if (!canDrag(table.dragRecords, dataRow[props.grid.props.foreignKey])) {
removeDragTopStyle(e);
removeDragBottomStyle(e);
setDragIcon(table.rows);
e.dataTransfer.dropEffect = 'none';
} else {
if (e.target.nodeName === 'TD' && e.offsetY <= gridTrMiddleHeightComputed.value && !dragRecordsContains(dataRow)) {
removeDragBottomStyle(e);
addDragTopStyle(e);
} else if (e.target.nodeName === 'TD' && e.offsetY > gridTrMiddleHeightComputed.value && !dragRecordsContains(dataRow)) {
removeDragTopStyle(e);
addDragBottomStyle(e);
}
if (!Tools.isUndefinedOrNull(dataRow[props.grid.props.foreignKey])) {
setDragIcon(table.rows, dataRow[props.grid.props.foreignKey]);
} else {
setDragIcon(table.rows);
}
e.dataTransfer.dropEffect = 'move';
}
};
//
const removeRecord = (arr, removeData) => {
let index = 0;
for (let i = 0; i < arr.length; i++) {
if (arr[i][props.rowKey] === removeData[props.rowKey]) {
arr.splice(i, 1);
index = i;
break;
} else if (arr[i].children && arr[i].children.length > 0) {
const a = removeRecord(arr[i].children, removeData);
if (a > 0) {
index = a;
}
}
}
return index;
};
//
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.dndOrderBy] = index + 1;
//
updateOrderData.push({ ...toRaw(item), children: null });
});
};
//
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]) {
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) {
childrenResetOrder(e, arr[i].children, addData, targetData, dragIndex, targetIndex);
}
}
};
const dragLessThanTarget = (dragIndex, targetIndex) => {
if (dragIndex < targetIndex) {
return true;
}
return false;
};
//
const resetOrderDataHandler = (e, dragRecords, targetData, targetIndex) => {
dragRecords.forEach((item) => {
//
const itemIndex = removeRecord(table.rows, item);
if (Tools.isEmpty(targetData[props.grid.props.foreignKey])) {
nodeSplice(e, itemIndex, targetIndex, item, targetData, table.rows);
item[props.grid.props.foreignKey] = null;
setOrder(table.rows);
} else {
childrenResetOrder(e, table.rows, item, targetData, itemIndex, targetIndex);
}
});
};
//
const resetOrder = (e, dragRecords, arr, targetData) => {
for (let i = 0; i < arr.length; i++) {
if (arr[i][props.rowKey] === targetData[props.rowKey]) {
resetOrderDataHandler(e, dragRecords, targetData, i);
break;
} else if (arr[i].children && arr[i].children.length > 0) {
resetOrder(e, dragRecords, arr[i].children, targetData);
}
}
};
//
const onDrop = (e, dataRow) => {
e.preventDefault();
removeDragTopStyle(e);
removeDragBottomStyle(e);
setDragIcon(table.rows);
if (
table.dragRecords.findIndex((item) => {
return item[props.rowKey] === dataRow[props.rowKey];
}) > -1
) {
return;
}
//
updateOrderData.splice(0, updateOrderData.length);
//
resetOrder(e, table.dragRecords, table.rows, dataRow);
//
if (props.grid.props.dndMode === dndMode.server && updateOrderData?.length > 0) {
props.updates(updateOrderData);
}
props.afterDragAndDrop(updateOrderData);
};
const click = (evt, row, rowIndex) => {
const selected = props.grid.getSelectedRow();
if (
props.grid.props.localMode &&
table.bodyEditStatus !== editStatus.none &&
props.grid.props.selectMode === selectMode.row &&
selected &&
row[props.rowKey] !== selected[props.rowKey]
) {
// 退
table.bodyEditStatus = editStatus.none;
} else if (table.bodyEditStatus === editStatus.none && props.grid.props.selectMode !== selectMode.none) {
if (!evt.ctrlKey) {
props.grid.clearSelected();
}
row[table.selectedField] = true;
if (props.grid.props.onRowClick) {
props.grid.props.onRowClick({
grid: props.grid,
evt: evt,
row: row,
index: rowIndex,
});
}
}
};
const dbClick = (evt, row, rowIndex) => {
props.gridRowDbClick(evt, row, rowIndex);
};
const setComponentRef = (el, row, col) => {
if (el && !Tools.isEmpty(col.type)) {
componentRef.value[row[props.rowKey] + '_' + col.name] = el;
}
};
const setTdRef = (el, col) => {
if (el && !tdRef.value[col['name']]) {
tdRef.value[col['name']] = el;
}
};
const getComponentOneRef = () => {
return componentOneRef.value;
};
const getComponentRef = () => {
return componentRef.value;
};
onMounted(() => {
Object.keys(tdRef.value).forEach((item) => {
tdWidth.value[item] = tdRef.value[item].$el.clientWidth;
});
});
defineExpose({
getComponentRef,
getComponentOneRef,
});
</script>
<style lang="css">
.gridTdDiv {
pointer-events: none;
}
</style>

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

File diff suppressed because it is too large

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

@ -84,3 +84,10 @@
.w-grid .q-table--cell-separator tbody tr:last-child td {
border-bottom-width: 1px;
}
/*
* cell
* 数据最后一行底部边框 (当元素具有.w-grid.q-table--cell-separator时表示表格全屏)
*/
.w-grid.q-table--cell-separator tbody tr:last-child td {
border-bottom-width: 1px;
}

216
io.sc.platform.core.frontend/src/platform/components/grid/extra/Editor.vue

@ -0,0 +1,216 @@
<template>
<w-dialog
ref="dialogRef"
v-bind="tools.props.editor.dialog"
:title="dialog.dialogTitle"
:buttons="dialogButtonsComputed"
@hide="
() => {
saveLoading = false;
}
"
>
<w-form ref="dialogFormRef" v-bind="tools.props.editor.form" class="pt-1.5 px-1.5"></w-form>
</w-dialog>
</template>
<script setup lang="ts">
import { $t, Tools } from '@/platform';
import { computed, inject, reactive, ref } from 'vue';
import { Constant, GridTools } from '../ts/index';
const tools = <GridTools>inject('tools');
const dialogRef = ref();
const dialogFormRef = ref();
const props = defineProps({});
const dialogButtonsComputed = computed(() => {
if (tools.props.editor?.dialog?.buttons) {
return [...tools.props.editor.dialog.buttons, ...dialog.dialogButtons];
}
return dialog.dialogButtons;
});
const save = async () => {
saveLoading.value = true;
const formStatus = dialogFormRef.value.getStatus();
const validate = await dialogFormRef.value.validate();
if (validate) {
let dialogFormData = dialogFormRef.value.getData();
const selected = tools.apiFM.getData.getSelectedRow();
if (formStatus === Constant.FORM_STATUS.EDIT && selected && selected[tools.props.primaryKey] && Tools.isEmpty(dialogFormData[tools.props.primaryKey])) {
dialogFormData[tools.props.primaryKey] = selected[tools.props.primaryKey];
}
//
const eventResult = tools.em.beforeEditorDataSubmit(dialogFormData);
dialogFormData = eventResult.data;
if (eventResult.submit) {
setForeignKey(dialogFormData, formStatus, selected);
if (formStatus === Constant.FORM_STATUS.EDIT) {
//
dialogFormData = { ...selected, ...dialogFormData };
}
if (tools.props.localMode) {
localModeSave(dialogFormData, formStatus, eventResult.closeDialog);
} else {
serverSave(dialogFormData, formStatus, selected, eventResult.closeDialog);
}
} else {
saveLoading.value = false;
if (eventResult.closeDialog) {
dialogRef.value.hide();
}
}
} else {
saveLoading.value = false;
}
};
const saveLoading = ref(false);
const dialog = reactive({
dialogTitle: $t('action.addNew'),
dialogButtons: [
{
icon: 'beenhere',
labelI18nKey: tools.props.localMode ? 'confirm' : 'action.submit',
label: $t(tools.props.localMode ? 'confirm' : 'action.submit'),
loading: saveLoading.value,
click: () => {
save();
},
},
],
});
//
const addTreeRow = (row) => {
if (Tools.isEmpty(row[tools.props.foreignKey])) {
tools.table.rows.push(row);
} else {
const parent = tools.dataFM.getRow(tools.table.rows, row[tools.props.foreignKey], true);
if (parent) {
if (parent['children'] && Array.isArray(parent['children'])) {
parent['children'].push(row);
} else {
parent['children'] = [row];
}
}
}
};
const addData = (dialogFormData) => {
if (tools.props.tree) {
addTreeRow(dialogFormData);
tools.dataFM.setExtraProperty(tools.table.rows);
} else {
tools.apiFM.localMode.addLocalData(dialogFormData, undefined);
}
};
const updateData = (dialogFormData) => {
const selected = tools.apiFM.getData.getSelectedRow();
if (!selected) {
return;
}
if (Tools.isEmpty(dialogFormData[tools.props.primaryKey])) {
dialogFormData[tools.props.primaryKey] = selected[tools.props.primaryKey];
}
dialogFormData[tools.props.selectedField] = true;
if (selected['children']) {
dialogFormData['children'] = selected['children'];
}
tools.apiFM.localMode.updateLocalData(dialogFormData);
};
const localModeSave = (dialogFormData: any, formStatus: string, closeDialog: boolean) => {
if (isAdd(formStatus)) {
addData(dialogFormData);
} else {
updateData(dialogFormData);
}
saveLoading.value = false;
if (closeDialog) {
dialogRef.value.hide();
}
};
const serverSave = (dialogFormData: any, formStatus: string, selected: any, closeDialog: boolean) => {
const method = getMethod(formStatus);
const url = getUrl(formStatus, selected);
const callback = (resp: any) => {
saveLoading.value = false;
if (closeDialog) {
dialogRef.value.hide();
}
if (tools.props.refreshData || !tools.props.tree) {
tools.apiFM.operator.refreshGrid();
} else if (resp.data && isAdd(formStatus)) {
addData(resp.data);
} else if (resp.data) {
updateData(resp.data);
}
};
const errorCallback = () => {
saveLoading.value = false;
};
tools.reqApiFM.save(method, dialogFormData, url, dialogFormRef.value, callback, errorCallback);
};
const setForeignKey = (dialogFormData: any, formStatus: string, selected: any) => {
if (formStatus === Constant.FORM_STATUS.ADD) {
dialogFormData[tools.props.foreignKey] = null;
} else if (formStatus === Constant.FORM_STATUS.ADD_CHILD) {
dialogFormData[tools.props.foreignKey] = selected[tools.props.primaryKey];
} else if ((formStatus === Constant.FORM_STATUS.EDIT || formStatus === Constant.FORM_STATUS.CLONE) && selected[tools.props.foreignKey]) {
dialogFormData[tools.props.foreignKey] = selected[tools.props.foreignKey];
}
};
const isAdd = (formStatus: string) => {
return (
formStatus === Constant.FORM_STATUS.ADD ||
formStatus === Constant.FORM_STATUS.CLONE ||
formStatus === Constant.FORM_STATUS.ADD_TOP ||
formStatus === Constant.FORM_STATUS.ADD_CHILD
);
};
const getMethod = (formStatus: string) => {
if (isAdd(formStatus)) {
return 'POST';
} else {
return 'PUT';
}
};
const getUrl = (formStatus: string, selected: any) => {
if (isAdd(formStatus)) {
if (!Tools.isEmpty(tools.table.url.addDataUrl)) {
return tools.table.url.addDataUrl;
} else {
return tools.table.url.dataUrl;
}
} else {
if (!Tools.isEmpty(tools.table.url.editDataUrl)) {
return tools.table.url.editDataUrl + '/' + selected[tools.props.primaryKey];
} else {
return tools.table.url.dataUrl + '/' + selected[tools.props.primaryKey];
}
}
};
const resetButtonLabel = () => {
dialog.dialogButtons[0].label = $t(dialog.dialogButtons[0].labelI18nKey);
};
const getDialog = () => {
return dialogRef.value;
};
const getForm = () => {
return dialogFormRef.value;
};
defineExpose({
resetButtonLabel,
getDialog,
getForm,
});
</script>
<style lang="css"></style>

71
io.sc.platform.core.frontend/src/platform/components/grid/extra/ViewPanel.vue

@ -0,0 +1,71 @@
<template>
<w-drawer ref="drawerRef" :title="$t('action.view')" v-bind="tools.props.viewer?.drawer">
<div class="p-2.5">
<w-info-panel ref="infoRef" v-bind="tools.props.viewer?.panel" :info="infoArray"></w-info-panel>
</div>
</w-drawer>
</template>
<script setup lang="ts">
import { $t, NotifyManager, t } from '@/platform';
import { inject, ref } from 'vue';
import { GridTools } from '../ts/index';
const tools = <GridTools>inject('tools');
const drawerRef = ref();
const infoRef = ref();
const gridColumnObjects = GridTools.arrayToObject(tools.table.originalColumns, 'name');
const props = defineProps({});
const infoArray = ref(<any>[]);
const view = () => {
const selected = tools.apiFM.getData.getSelectedRow();
if (!selected) {
NotifyManager.warn(t('action.view.tip'));
} else {
infoArray.value = [];
if (tools.props.viewer.panel.fields && tools.props.viewer.panel.fields.length > 0) {
for (let item of tools.props.viewer.panel.fields) {
const value = formatValue(item, selected);
infoArray.value.push({ label: item.label, value: value, originalValue: selected[item.name] });
}
} else {
for (let key in gridColumnObjects) {
const item = gridColumnObjects[key];
const value = formatValue(item, selected);
infoArray.value.push({ label: item.label, value: value, originalValue: selected[key] });
}
}
drawerRef.value.show();
}
};
const formatValue = (item, selected) => {
let value = selected[item.name];
try {
if (item.format) {
value = item.format(value, selected);
} else if (gridColumnObjects[item.name] && gridColumnObjects[item.name].format) {
value = gridColumnObjects[item.name].format(value, selected);
}
} catch (error) {
console.error('[w-grid]column `' + item.name + '` format error:', error);
}
return value;
};
const getViewerDrawer = () => {
return drawerRef.value;
};
const getInfoPanel = () => {
return infoRef.value;
};
defineExpose({
getViewerDrawer,
getInfoPanel,
view,
});
</script>
<style lang="css"></style>

58
io.sc.platform.core.frontend/src/platform/components/grid/extra/advanced-query/AdvancedQuery.vue

@ -0,0 +1,58 @@
<template>
<w-query-builder ref="queryBuilderRef" v-model="tools.table.advancedQueryModelValue" :options="options"></w-query-builder>
</template>
<script setup lang="ts">
import { inject, onBeforeMount, ref } from 'vue';
import { GridTools } from '../../ts/index';
const queryBuilderRef = ref();
const tools = <GridTools>inject('tools');
const options: any = [];
const buildOptions = () => {
options.splice(0, options.length - 1);
tools.props.queryFormFields.forEach((item) => {
const field = {
label: item['label'],
value: item['name'],
useComponent: {
...item,
label: undefined, // label
name: undefined, // name
type: typeConversion(item['type']), //
},
};
options.push(field);
});
};
/**
* 对于特定的类型使用 w-query-build 组件时会存在矛盾点.
* 比如普通查询配置使用日期范围但是在w-query-build中本身就提供范围选项所以需要将其转换成普通日期类型
* @param type
*/
const typeConversion = (type: string) => {
let result = '';
switch (type) {
case 'w-date-range':
result = 'w-date';
break;
default:
result = type;
break;
}
return result;
};
onBeforeMount(() => {
buildOptions();
});
const reset = () => {
queryBuilderRef.value.clearValue();
};
defineExpose({
reset: reset,
});
</script>

21
io.sc.platform.core.frontend/src/platform/components/grid/extra/append/AppendContent.vue

@ -0,0 +1,21 @@
<template>
<template v-if="typeof props.value === 'object' && props.value.componentType">
<component :is="props.value.componentType" v-bind="props.value.attrs"></component>
</template>
<template v-else>
<span v-dompurify-html="!Tools.isEmpty(props.value) ? props.value : ''"></span>
</template>
</template>
<script setup lang="ts">
import { Tools } from '@/platform';
const props = defineProps({
value: {
type: [Object, String, Number],
default: () => {
return '';
},
},
});
</script>

39
io.sc.platform.core.frontend/src/platform/components/grid/extra/append/AppendRow.vue

@ -0,0 +1,39 @@
<template>
<template v-if="showAppendRowsComputed">
<q-tr v-for="(appendRow, rowIndex) in tools.props.appendRows" :key="rowIndex" :no-hover="true" :class="''">
<q-td v-for="(col, colIndex) in appendRow" :key="colIndex" :rowspan="col['rowSpan'] ? col['rowSpan'] : 1" :colspan="col['colSpan'] ? col['colSpan'] : 1">
<template v-if="typeof col['value'] === 'function'">
<AppendContent :value="col.value({ grid: tools.instance, rows: tools.apiFM.getData.getRows() })"></AppendContent>
</template>
<template v-else>
<span v-dompurify-html="!Tools.isEmpty(col['value']) ? col['value'] : ''"></span>
</template>
</q-td>
</q-tr>
</template>
</template>
<script setup lang="ts">
import { inject, computed } from 'vue';
import { Tools } from '@/platform';
import { GridTools } from '../../ts/index';
import AppendContent from './AppendContent.vue';
const tools = <GridTools>inject('tools');
const props = defineProps({
scope: {
//
type: Object,
default: () => {
return {};
},
},
});
const showAppendRowsComputed = computed(() => {
if (Array.isArray(tools.props.appendRows) && tools.props.appendRows.length > 0) {
return tools.dataFM.isLastRow(props.scope.row);
}
return false;
});
</script>

54
io.sc.platform.core.frontend/src/platform/components/grid/extra/config/AloneGroup.vue

@ -0,0 +1,54 @@
<template>
<q-expansion-item label="分组">
<q-card>
<q-card-section>
<template v-for="col in tools.cm.displayColumns.value" :key="col.name">
<q-item v-if="col !== Constant.FIELD_NAMES.SORT_NO" v-ripple tag="label" dense>
<q-item-section side>
<q-checkbox v-model="dynamicModel[col]" dense @update:model-value="groupByFieldChange(col)" />
</q-item-section>
<q-item-section>
<q-item-label>{{ displayColumnLabel[col] }}</q-item-label>
</q-item-section>
</q-item>
</template>
</q-card-section>
</q-card>
</q-expansion-item>
</template>
<script setup lang="ts">
import { inject, ref } from 'vue';
import { GridTools, Constant } from '../../ts/index';
const tools = <GridTools>inject('tools');
const dynamicModel = ref({});
const displayColumnLabel = ref({});
const getColumn = (name) => {
return tools.table.columns.find((item) => item['name'] === name);
};
tools.cm.displayColumns.value.forEach((item) => {
const column = getColumn(item);
if (column) {
displayColumnLabel.value[item] = column.label;
}
dynamicModel.value[item] = item === tools.table.configStore.aloneGroupByField || false;
});
const groupByFieldChange = (col: string) => {
const value = dynamicModel.value[col];
if (value) {
//
Object.keys(dynamicModel.value).forEach((item) => {
if (item !== col) {
dynamicModel.value[item] = false;
}
});
tools.table.configStore.aloneGroupByField = col;
} else {
tools.table.configStore.aloneGroupByField = undefined;
}
tools.dataFM.setExtraProperty(tools.table.rows);
};
</script>

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

@ -0,0 +1,36 @@
<template>
<q-item :clickable="true" @click="click">
<q-item-section>
<q-item-label>复选框</q-item-label>
</q-item-section>
<q-item-section side>
<q-btn-group outline flat dense unelevated spread>
<q-btn
:color="tools.table.configStore.useCheckboxSelection ? 'primary' : ''"
:outline="tools.table.configStore.useCheckboxSelection ? false : true"
dense
label="显示"
unelevated
/>
<q-btn
:color="tools.table.configStore.useCheckboxSelection ? '' : 'primary'"
:outline="tools.table.configStore.useCheckboxSelection ? true : false"
dense
label="隐藏"
unelevated
/>
</q-btn-group>
</q-item-section>
</q-item>
</template>
<script setup lang="ts">
import { inject } from 'vue';
import { GridTools } from '../../ts/index';
const tools = <GridTools>inject('tools');
const click = () => {
tools.table.configStore.useCheckboxSelection = !tools.table.configStore.useCheckboxSelection;
tools.opFM.resetStyleVariableValue(100);
};
</script>

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

@ -0,0 +1,60 @@
<template>
<q-list padding style="min-width: 250px">
<!-- 全屏 -->
<Fullscreen :scope="props.scope"></Fullscreen>
<q-separator />
<!-- 复选框 -->
<CheckboxSelection></CheckboxSelection>
<q-separator />
<!-- 序号 -->
<template v-if="!tools.props.tree">
<SortNo></SortNo>
<q-separator />
</template>
<!-- 分割线 -->
<Separator></Separator>
<q-separator />
<!-- 紧凑模式 -->
<Dense></Dense>
<q-separator />
<!-- 独立分组 -->
<template v-if="!tools.props.tree && tools.props.groupMode === Constant.GROUP_MODE.ALONE">
<AloneGorup></AloneGorup>
<q-separator />
</template>
<!-- 固定列 -->
<StickyColumn></StickyColumn>
<q-separator />
<!-- 显示列 -->
<DisplayColumn></DisplayColumn>
</q-list>
</template>
<script setup lang="ts">
import { inject } from 'vue';
import { Constant, GridTools } from '../../ts/index';
import CheckboxSelection from './CheckboxSelection.vue';
import Dense from './Dense.vue';
import DisplayColumn from './DisplayColumn.vue';
import Fullscreen from './Fullscreen.vue';
import Separator from './Separator.vue';
import SortNo from './SortNo.vue';
import StickyColumn from './StickyColumn.vue';
import AloneGorup from './AloneGroup.vue';
const tools = <GridTools>inject('tools');
const props = defineProps({
scope: {
type: Object,
default: () => {
return {};
},
},
});
</script>

57
io.sc.platform.core.frontend/src/platform/components/grid/extra/config/Dense.vue

@ -0,0 +1,57 @@
<template>
<q-expansion-item label="紧凑模式">
<q-card>
<q-card-section>
<q-item v-ripple tag="label" dense>
<q-item-section side>
<q-checkbox v-model="tools.table.configStore.dense" dense @update:model-value="denseChange" />
</q-item-section>
<q-item-section>
<q-item-label>紧凑</q-item-label>
</q-item-section>
</q-item>
<q-item v-ripple tag="label" dense>
<q-item-section side>
<q-checkbox v-model="tools.table.configStore.denseToolbar" dense @update:model-value="denseChange" />
</q-item-section>
<q-item-section>
<q-item-label>按钮栏紧凑</q-item-label>
</q-item-section>
</q-item>
<q-item v-ripple tag="label" dense>
<q-item-section side>
<q-checkbox v-model="tools.table.configStore.denseHeader" dense @update:model-value="denseChange" />
</q-item-section>
<q-item-section>
<q-item-label>列头紧凑</q-item-label>
</q-item-section>
</q-item>
<q-item v-ripple tag="label" dense>
<q-item-section side>
<q-checkbox v-model="tools.table.configStore.denseBody" dense @update:model-value="denseChange" />
</q-item-section>
<q-item-section>
<q-item-label>内容紧凑</q-item-label>
</q-item-section>
</q-item>
<q-item v-ripple tag="label" dense>
<q-item-section side>
<q-checkbox v-model="tools.table.configStore.denseBottom" dense @update:model-value="denseChange" />
</q-item-section>
<q-item-section>
<q-item-label>分页栏紧凑</q-item-label>
</q-item-section>
</q-item>
</q-card-section>
</q-card>
</q-expansion-item>
</template>
<script setup lang="ts">
import { inject } from 'vue';
import { GridTools } from '../../ts/index';
const tools = <GridTools>inject('tools');
const denseChange = () => {
tools.opFM.resetStyleVariableValue(0);
};
</script>

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

@ -0,0 +1,47 @@
<template>
<q-expansion-item 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-item-section>
<q-item-section>
<q-item-label>{{ col.label || col.name }}</q-item-label>
</q-item-section>
</q-item>
</template>
</q-card-section>
</q-card>
</q-expansion-item>
</template>
<script setup lang="ts">
import { inject } from 'vue';
import { GridTools } from '../../ts/index';
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 {
// }
return true;
};
</script>

35
io.sc.platform.core.frontend/src/platform/components/grid/extra/config/Fullscreen.vue

@ -0,0 +1,35 @@
<template>
<q-item
:clickable="true"
@click="
() => {
scope.toggleFullscreen();
tools.table.configStore.showConfigPanel = false;
}
"
>
<q-item-section>
<q-item-label>全屏</q-item-label>
</q-item-section>
<q-item-section side>
<q-btn-group outline flat dense unelevated spread>
<q-btn :color="!scope.inFullscreen ? 'primary' : ''" :outline="scope.inFullscreen ? true : false" dense label="正常" unelevated />
<q-btn :color="scope.inFullscreen ? 'primary' : ''" :outline="!scope.inFullscreen ? true : false" dense label="全屏" unelevated />
</q-btn-group>
</q-item-section>
</q-item>
</template>
<script setup lang="ts">
import { inject } from 'vue';
import { GridTools } from '../../ts/index';
const tools = <GridTools>inject('tools');
const props = defineProps({
scope: {
type: Object,
default: () => {
return {};
},
},
});
</script>

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

@ -0,0 +1,96 @@
<template>
<q-expansion-item label="分割线">
<q-card>
<q-card-section>
<q-item v-ripple tag="label" dense>
<q-item-section side>
<q-checkbox v-model="separatorHorizontal" dense @update:model-value="separatorChange('horizontal', separatorHorizontal)" />
</q-item-section>
<q-item-section>
<q-item-label>水平</q-item-label>
</q-item-section>
</q-item>
<q-item v-ripple tag="label" dense>
<q-item-section side>
<q-checkbox v-model="separatorVertical" dense @update:model-value="separatorChange('vertical', separatorVertical)" />
</q-item-section>
<q-item-section>
<q-item-label>垂直</q-item-label>
</q-item-section>
</q-item>
<q-item v-ripple tag="label" dense>
<q-item-section side>
<q-checkbox v-model="separatorCell" dense @update:model-value="separatorChange('cell', separatorCell)" />
</q-item-section>
<q-item-section>
<q-item-label>单元格</q-item-label>
</q-item-section>
</q-item>
<q-item v-ripple tag="label" dense>
<q-item-section side>
<q-checkbox v-model="separatorNone" dense @update:model-value="separatorChange('none', separatorNone)" />
</q-item-section>
<q-item-section>
<q-item-label></q-item-label>
</q-item-section>
</q-item>
</q-card-section>
</q-card>
</q-expansion-item>
</template>
<script setup lang="ts">
import { inject, ref, watch } from 'vue';
import { GridTools } from '../../ts/index';
const tools = <GridTools>inject('tools');
const separatorHorizontal = ref(false);
const separatorVertical = ref(false);
const separatorCell = ref(false);
const separatorNone = ref(false);
const setSeparatorValue = (value) => {
if (value === 'horizontal') {
separatorHorizontal.value = true;
separatorVertical.value = false;
separatorCell.value = false;
separatorNone.value = false;
} else if (value === 'vertical') {
separatorHorizontal.value = false;
separatorVertical.value = true;
separatorCell.value = false;
separatorNone.value = false;
} else if (value === 'cell') {
separatorHorizontal.value = false;
separatorVertical.value = false;
separatorCell.value = true;
separatorNone.value = false;
} else if (value === 'none') {
separatorHorizontal.value = false;
separatorVertical.value = false;
separatorCell.value = false;
separatorNone.value = true;
}
};
if (tools.table.configStore.separator) {
setSeparatorValue(tools.table.configStore.separator);
}
const separatorChange = (separator, value) => {
if (!value) {
setSeparatorValue(tools.table.configStore.separator);
} else {
tools.table.configStore.separator = separator;
}
};
watch(
() => tools.table.configStore.separator,
(newVal, oldVal) => {
if (newVal) {
setSeparatorValue(newVal);
}
},
);
</script>

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

@ -0,0 +1,36 @@
<template>
<q-item :clickable="true" @click="click">
<q-item-section>
<q-item-label>序号</q-item-label>
</q-item-section>
<q-item-section side>
<q-btn-group outline flat dense unelevated spread>
<q-btn
:color="tools.table.configStore.showSortNo ? 'primary' : ''"
:outline="tools.table.configStore.showSortNo ? false : true"
dense
label="显示"
unelevated
/>
<q-btn
:color="tools.table.configStore.showSortNo ? '' : 'primary'"
:outline="tools.table.configStore.showSortNo ? true : false"
dense
label="隐藏"
unelevated
/>
</q-btn-group>
</q-item-section>
</q-item>
</template>
<script setup lang="ts">
import { inject } from 'vue';
import { GridTools } from '../../ts/index';
const tools = <GridTools>inject('tools');
const click = () => {
tools.table.columns[0].showIf = !tools.table.columns[0].showIf;
tools.table.configStore.showSortNo = !tools.table.configStore.showSortNo;
tools.opFM.resetStyleVariableValue(100);
};
</script>

44
io.sc.platform.core.frontend/src/platform/components/grid/extra/config/StickyColumn.vue

@ -0,0 +1,44 @@
<template>
<q-item :clickable="false">
<q-item-section>
<q-item-label class="nowrap text-nowrap">固定列</q-item-label>
</q-item-section>
<q-item-section side>
<q-select
v-model="tools.table.configStore.stickyNum"
emit-value
map-options
:hide-bottom-space="true"
:hide-hint="true"
:outlined="true"
:dense="true"
:options="stickyOptions"
@update:model-value="
() => {
tools.opFM.resetStyleVariableValue(500);
}
"
>
</q-select>
</q-item-section>
</q-item>
</template>
<script setup lang="ts">
import { inject } from 'vue';
import { GridTools } from '../../ts/index';
const tools = <GridTools>inject('tools');
const stickyOptions = [
{ label: '不固定', value: 0 },
{ label: '固定1列', value: 1 },
{ label: '固定2列', value: 2 },
{ label: '固定3列', value: 3 },
{ label: '固定4列', value: 4 },
{ label: '固定5列', value: 5 },
{ label: '固定6列', value: 6 },
{ label: '固定7列', value: 7 },
{ label: '固定8列', value: 8 },
{ label: '固定9列', value: 9 },
{ label: '固定10列', value: 10 },
];
</script>

54
io.sc.platform.core.frontend/src/platform/components/grid/extra/group/GroupTr.vue

@ -0,0 +1,54 @@
<template>
<template v-if="props.scope.pageIndex === 0 && tools.table.configStore.aloneGroupRecords.length > 0">
<template v-for="(record, index) in tools.table.configStore.aloneGroupRecords" :key="index">
<q-tr ref="trRef" @click.stop="changeExpand(record)">
<q-td :colspan="tools.cm.displayColumnNum.value">
<q-btn
:size="tools.table.configStore.dense || tools.table.configStore.denseBody ? 'xs' : 'sm'"
color="primary"
round
dense
:icon="record['expand'] ? 'remove' : 'add'"
@click.stop="changeExpand(record)"
/>
<span class="ml-3 font-medium">{{ record['groupName'] }}</span>
</q-td>
</q-tr>
<template v-if="record['expand']">
<Tr v-for="(groupRow, groupRowIndex) in record['rows']" :key="groupRowIndex" :scope="buildScope(groupRow)"></Tr>
</template>
</template>
</template>
</template>
<script setup lang="ts">
import { inject } from 'vue';
import Tr from '../../Tr.vue';
import { GridTools, Constant } from '../../ts/index';
const tools = <GridTools>inject('tools');
const props = defineProps({
scope: {
//
type: Object,
default: () => {
return {};
},
},
});
const changeExpand = (record: any) => {
record['expand'] = !record['expand'];
};
const buildScope = (groupRow: any) => {
const result = GridTools.buildChildrenScope(
props.scope,
groupRow,
groupRow[Constant.FIELD_NAMES.ROW_INDEX],
tools.props.selectedField,
tools.props.tickedField,
);
return result;
};
</script>

97
io.sc.platform.core.frontend/src/platform/components/grid/extra/inline-edit/CellEditor.vue

@ -0,0 +1,97 @@
<template>
<w-dialog ref="dialogRef" :title="dialog.title" v-bind="tools.props.editor?.cellEditor" :buttons="dialog.buttons">
<w-form ref="dialogFormRef" :cols-num="1" :fields="fieldsComputed" class="pt-1.5 px-1.5"></w-form>
</w-dialog>
</template>
<script setup lang="ts">
import { t, Tools } from '@/platform';
import { computed, inject, ref, toRaw } from 'vue';
import { Constant, GridTools } from '../../ts/index';
const dialogRef = ref();
const dialogFormRef = ref();
const tools = <GridTools>inject('tools');
const fieldsComputed = computed(() => {
const fields = <any>[];
const cellSelected = tools.table.store.cellSelected;
if (cellSelected) {
const column = tools.editFM.getEditorFieldByName(cellSelected.colName);
if (column) {
fields.push({
...column,
name: column['name'],
type: column['type'],
label: column['label'],
defaultValue: cellSelected.value,
});
}
}
return fields;
});
const saveLoading = ref(false);
const dialog = {
title: t('action.edit'),
buttons: [
{
icon: 'beenhere',
labelI18nKey: tools.props.localMode ? 'confirm' : 'action.submit',
label: t(tools.props.localMode ? 'confirm' : 'action.submit'),
loading: saveLoading.value,
click: async () => {
saveLoading.value = true;
const result = await dialogFormRef.value.validate();
if (result) {
const cellSelected = tools.table.store.cellSelected;
if (!cellSelected) {
return;
}
const data = dialogFormRef.value.getData();
const row = tools.dataFM.getRow(tools.table.rows, cellSelected.row[Constant.FIELD_NAMES.ROW_KEY], false);
if (row) {
row[cellSelected.colName] = data[cellSelected.colName];
//
if (!tools.props.localMode) {
const data = GridTools.removeExtraFields(toRaw(row));
const url = getUrl(data[tools.props.primaryKey]);
const callback = () => {
saveLoading.value = false;
dialogRef.value.hide();
};
const errorCallback = () => {
saveLoading.value = false;
};
tools.reqApiFM.save('PUT', data, url, dialogFormRef.value, callback, errorCallback);
} else {
dialogRef.value.hide();
}
}
}
saveLoading.value = false;
},
},
],
};
const getUrl = (primaryKey) => {
if (!Tools.isEmpty(tools.table.url.editDataUrl)) {
return tools.table.url.editDataUrl + '/' + primaryKey;
} else {
return tools.table.url.dataUrl + '/' + primaryKey;
}
};
const getDialog = () => {
return dialogRef.value;
};
const getForm = () => {
return dialogFormRef.value;
};
defineExpose({
getDialog,
getForm,
});
</script>

71
io.sc.platform.core.frontend/src/platform/components/grid/extra/inline-edit/InlineEditComponent.vue

@ -0,0 +1,71 @@
<template>
<template v-if="isDirectlyBindComputed">
<component
:is="componentProps['type']"
:ref="(el) => tools.editFM.setComponentRef(el, props.row, props.col)"
v-bind="{ ...componentProps, label: undefined }"
v-model="gridRow[props.col['name']]"
bg-color="light-green-1"
@blur="
() => {
if (needExit()) {
tools.editFM.exitInlineEdit();
}
}
"
></component>
</template>
<template v-else>
<component
:is="componentProps['type']"
:ref="(el) => tools.editFM.setComponentRef(el, props.row, props.col)"
v-bind="{ ...componentProps, label: undefined }"
v-model="gridRow[Constant.FIELD_NAMES.ROW_OLD_VALUE][props.col['name']]"
bg-color="light-green-1"
@blur="
() => {
if (needExit()) {
tools.editFM.exitInlineEdit();
}
}
"
></component>
</template>
</template>
<script setup lang="ts">
import { computed, inject } from 'vue';
import { GridTools, Constant } from '../../ts/index';
const tools = <GridTools>inject('tools');
const props = defineProps({
col: {
type: Object,
default: () => {
return {};
},
},
row: {
type: Object,
default: () => {
return {};
},
},
});
const gridRow = tools.dataFM.getRow(tools.table.rows, props.row[Constant.FIELD_NAMES.ROW_KEY], false);
const componentProps = tools.editFM.getEditorFieldByName(props.col['name']);
/**
* 是否直接绑定数据
*/
const isDirectlyBindComputed = computed(() => {
return tools.props.localMode && tools.table.store.inlineEditStatus !== Constant.EDIT_STATUS.ROWS;
});
const needExit = () => {
if (tools.props.localMode && tools.props.selectMode === Constant.SELECT_MODE.CELL && tools.table.store.inlineEditStatus === Constant.EDIT_STATUS.CELL) {
return true;
}
return false;
};
</script>

249
io.sc.platform.core.frontend/src/platform/components/grid/extra/inline-edit/InlineEditToolbar.vue

@ -0,0 +1,249 @@
<template>
<q-tr v-if="showComputed">
<q-td :colspan="tools.cm.displayColumnNum.value">
<div class="editButton text-center">
<w-toolbar v-model="buttons" :dense="true" align="center" :grid="tools.instance"></w-toolbar>
</div>
</q-td>
</q-tr>
</template>
<script setup lang="ts">
import { NotifyManager, Tools, $t } from '@/platform';
import { computed, inject, ref } from 'vue';
import { Constant, GridTools } from '../../ts/index';
const tools = <GridTools>inject('tools');
const props = defineProps({
row: {
type: Object,
default: () => {
return {};
},
},
});
const buttons = ref([
{
label: '保存',
name: 'save',
icon: 'save',
color: 'primary',
outline: false,
click: async (args) => {
save(args);
},
},
{
label: '取消',
name: 'cancel',
icon: 'close',
color: 'blue-grey',
outline: false,
click: (args) => {
if (tools.table.store.inlineEditStatus === Constant.EDIT_STATUS.ROW || tools.table.store.inlineEditStatus === Constant.EDIT_STATUS.CELL) {
tools.dataFM.setOldValue(args.selected);
} else if (tools.table.store.inlineEditStatus === Constant.EDIT_STATUS.ROWS) {
tools.apiFM.getData.getRows().forEach((item) => {
tools.dataFM.setOldValue(item);
});
}
tools.table.store.inlineEditStatus = Constant.EDIT_STATUS.NONE;
},
},
]);
const showComputed = computed(() => {
if (tools.props.localMode && tools.table.store.inlineEditStatus !== Constant.EDIT_STATUS.ROWS) {
return false;
}
if (tools.table.store.inlineEditStatus === Constant.EDIT_STATUS.CELL) {
const selected = tools.apiFM.getData.getSelectedCell();
if (
selected?.colName &&
selected.colName === tools.table.store.cellSelected?.colName &&
selected.row[Constant.FIELD_NAMES.ROW_KEY] === props.row[Constant.FIELD_NAMES.ROW_KEY]
) {
return true;
}
return false;
} else if (tools.table.store.inlineEditStatus === Constant.EDIT_STATUS.ROW && tools.dataFM.isSelectedRow(props.row[Constant.FIELD_NAMES.ROW_KEY])) {
return true;
} else if (tools.table.store.inlineEditStatus === Constant.EDIT_STATUS.ROWS && tools.table.rows.length > 0 && isLastRowComputed.value) {
return true;
} else {
return false;
}
});
const isLastRowComputed = computed(() => {
return tools.dataFM.isLastRow(props.row);
});
const validate = async (refs) => {
let result = true;
for (let i = 0; i < refs.length; i++) {
const component = refs[i];
if (component && !Tools.isEmpty(component.validate)) {
const componentValidateResult = await component.validate();
if (!componentValidateResult) {
result = false;
break;
}
}
}
return result;
};
const save = async (args) => {
let refs: any = undefined;
if (tools.table.store.inlineEditStatus === Constant.EDIT_STATUS.CELL) {
refs = [tools.editFM.getComponentRef(args.selected[Constant.FIELD_NAMES.ROW_KEY], args.selectedColName)];
} else if (tools.table.store.inlineEditStatus === Constant.EDIT_STATUS.ROW) {
refs = tools.editFM.getComponentRefs(args.selected[Constant.FIELD_NAMES.ROW_KEY]);
} else if (tools.table.store.inlineEditStatus === Constant.EDIT_STATUS.ROWS) {
refs = tools.editFM.getComponentRefs([]);
}
const result = await validate(refs);
if (!result) {
NotifyManager.error('验证未通过');
} else {
if (tools.table.store.inlineEditStatus === Constant.EDIT_STATUS.ROW || tools.table.store.inlineEditStatus === Constant.EDIT_STATUS.CELL) {
rowSave(args);
} else if (tools.table.store.inlineEditStatus === Constant.EDIT_STATUS.ROWS) {
rowsSave(args);
}
}
};
const rowSave = (args) => {
let data = args.selected[Constant.FIELD_NAMES.ROW_OLD_VALUE];
let submitFlag = true;
let localeUpdateFlag = false;
let url = '';
//
tools.em.beforeEditorDataSubmit({
grid: tools.instance,
data: data,
callback: (handlerRequestParams: any | boolean, localeUpdate: boolean = false) => {
if (typeof handlerRequestParams === 'boolean' && handlerRequestParams === false) {
submitFlag = false;
} else {
data = handlerRequestParams;
}
localeUpdateFlag = localeUpdate;
},
});
if (localeUpdateFlag && submitFlag) {
// 访
tools.apiFM.localMode.updateLocalData(data);
tools.table.store.inlineEditStatus = Constant.EDIT_STATUS.NONE;
} else {
if (submitFlag) {
data = { ...args.selected, ...data };
if (!Tools.isEmpty(tools.table.url.editDataUrl)) {
url = tools.table.url.editDataUrl + '/' + args.selected[tools.props.primaryKey];
} else {
url = tools.table.url.dataUrl + '/' + args.selected[tools.props.primaryKey];
}
const callback = (resp) => {
if (tools.props.refreshData || !tools.props.tree) {
tools.apiFM.operator.refreshGrid();
} else if (resp.data) {
tools.apiFM.localMode.updateLocalData(data);
}
tools.editFM.exitInlineEdit();
};
const errorCallback = (error) => {
if (error?.response.data.code === 1001) {
NotifyManager.error('服务器验证未通过');
}
};
tools.reqApiFM.save('PUT', data, url, undefined, callback, errorCallback);
}
}
};
const rowsSave = (args) => {
let data = <any>[];
const rows = args.grid.getRows();
const isTree = tools.props.tree;
rows.forEach((item) => {
if (checkDataModified(item)) {
data.push(item[Constant.FIELD_NAMES.ROW_OLD_VALUE]);
}
if (isTree && item.children) {
const childrenData = treeDataPush(item.children);
if (childrenData.length > 0) {
data.push(...childrenData);
}
}
});
let submitFlag = true;
let localeUpdateFlag = false;
//
tools.em.afterEditorDataSubmit({
grid: tools.instance,
data: data,
callback: (handlerRequestParams: any | boolean, localeUpdate: boolean = false) => {
if (typeof handlerRequestParams === 'boolean' && handlerRequestParams === false) {
submitFlag = false;
} else {
data = handlerRequestParams;
}
localeUpdateFlag = localeUpdate;
},
});
if ((localeUpdateFlag && submitFlag) || tools.props.localMode) {
// 访
data.forEach((item) => {
tools.apiFM.localMode.updateLocalData(item);
});
tools.table.store.inlineEditStatus = Constant.EDIT_STATUS.NONE;
} else {
if (submitFlag) {
tools.reqApiFM.updates(data, (callbackData) => {
NotifyManager.info($t('tip.operationSuccess'));
if (tools.props.refreshData || !tools.props.tree) {
tools.apiFM.operator.refreshGrid();
} else if (!Tools.isEmpty(callbackData)) {
callbackData.forEach((item) => {
tools.apiFM.localMode.updateLocalData(item);
});
}
tools.editFM.exitInlineEdit();
});
}
}
};
//
const checkDataModified = (row) => {
const keys = Object.keys(row[Constant.FIELD_NAMES.ROW_OLD_VALUE]);
return keys.some((key) => row[key] !== row[Constant.FIELD_NAMES.ROW_OLD_VALUE][key]);
};
const treeDataPush = (arr) => {
const data = <any>[];
if (arr && arr.length > 0) {
arr.forEach((item) => {
if (checkDataModified(item)) {
data.push(item[Constant.FIELD_NAMES.ROW_OLD_VALUE]);
}
const childrenData = treeDataPush(item.children);
if (childrenData.length > 0) {
data.push(...childrenData);
}
});
}
return data;
};
</script>
<style lang="css">
.editButton {
position: sticky;
background-color: white;
left: 45%;
width: 150px;
}
</style>

48
io.sc.platform.core.frontend/src/platform/components/grid/ts/Base.ts

@ -0,0 +1,48 @@
import { Tools } from '@/platform';
import { GridTools } from './GridTools';
import { TableType } from './types/TableType';
import type { PropsType } from './types/PropsType';
/**
* w-grid
*/
export class Base {
/**
* w-grid
*/
props: PropsType;
/**
* w-grid 使
*/
table: TableType;
/**
* w-grid
*/
instance?: any;
/**
* w-grid
*/
tools?: GridTools;
constructor(props: PropsType, table: TableType) {
this.props = props;
this.table = table;
}
instanceError() {
if (Tools.isEmpty(this.instance)) {
throw new Error('[w-grid] The `instance` variable of `FunctionTools` class is undefined.');
}
}
tableError() {
if (Tools.isEmpty(this.table)) {
throw new Error('[w-grid] The `table` variable of `FunctionTools` class is undefined.');
}
}
// 设置组件实例
setInstance(instance: any, tools?: GridTools) {
this.instance = instance;
this.tools = tools;
}
}

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

@ -0,0 +1,328 @@
import { reactive } from 'vue';
import { Constant } from './constant/Constant';
import { TableType } from './types/TableType';
import type { PropsType } from './types/PropsType';
import { Init } from './Init';
import { getCssVar } from 'quasar';
import { Environment, Tools } from '@/platform';
import { ComputedManager } from './computed/ComputedManager';
import { EventManager } from './event/EventManager';
import { ExposeApiManager } from './expose-api/ExposeApiManager';
import { Criteria } from './function/Criteria';
import { DragAndDrop } from './function/DragAndDrop';
import { InlineEdit } from './function/InlineEdit';
import { Operator } from './function/Operator';
import { RequestApi } from './function/RequestApi';
import { RowData } from './function/RowData';
import { ButtonManager } from './toolbar/ButtonManager';
/**
* w-grid
*/
export class GridTools {
/**
* w-grid
*/
props: PropsType;
/**
* w-grid 使
*/
table: TableType;
/**
* w-grid
*/
instance?: any;
/**
*
*/
em: EventManager;
/**
*
*/
cm: ComputedManager;
/**
*
*/
bm: ButtonManager;
/**
* api
*/
apiFM: ExposeApiManager;
/**
*
*/
dataFM: RowData;
/**
*
*/
opFM: Operator;
/**
*
*/
editFM: InlineEdit;
/**
*
*/
dndFM: DragAndDrop;
/**
* API函数管理器
*/
reqApiFM: RequestApi;
/**
* criteria
*/
criteriaFM: Criteria;
constructor(props: PropsType) {
this.props = props;
const darkCssVar = getCssVar('dark') || '';
const gc = Environment.getConfigure();
const dense = props.dense !== undefined ? props.dense : false;
const denseBottom = props.denseBottom !== undefined ? props.denseBottom : false;
let bodyCellHeight = 48;
if (denseBottom) {
bodyCellHeight = 24;
} else if (dense !== false) {
bodyCellHeight = 24;
}
let alongGroupByField: any = undefined;
if (this.props.groupMode === Constant.GROUP_MODE.ALONE) {
if (typeof this.props.groupByField === 'string') {
alongGroupByField = this.props.groupByField;
} else if (Array.isArray(this.props.groupByField)) {
alongGroupByField = this.props.groupByField[0];
}
}
this.table = reactive({
originalColumns: [...props.columns],
columns: Init.initColumn(props.columns, props),
rows: [],
queryCriteria: { ...props.queryCriteria },
moreQueryStatus: false,
advancedQueryStatus: false,
advancedQueryModelValue: undefined,
queryFormDisplayFields: [],
componentRef: {
getTableRef: () => {},
getTopRef: () => {},
getHeaderRef: () => {},
getBodyRef: () => {},
getEditorRef: () => {},
getCellEditorRef: () => {},
getViewRef: () => {},
},
color: {
darkBgColor: darkCssVar,
headBgColor: gc.theme.dark ? darkCssVar : gc.theme?.grid?.headBgColor || '#f5f7fa',
borderColor: gc.theme?.grid?.borderColor || 'rgba(0, 0, 0, 0.12)',
stickyBgColor: gc.theme.dark ? darkCssVar : gc.theme?.grid?.stickyBgColor || '#ffffff',
},
url: {
dataUrl: props.dataUrl,
fetchDataUrl: props.fetchDataUrl,
addDataUrl: props.addDataUrl,
editDataUrl: props.editDataUrl,
removeDataUrl: props.removeDataUrl,
},
configStore: {
showConfigPanel: false,
isFullscreen: false,
separator: props.separator,
stickyNum: props.stickyNum,
useCheckboxSelection: props.checkboxSelection,
showSortNo: props.sortNo,
dense: dense,
denseToolbar: props.denseToolbar !== undefined ? props.denseToolbar : false,
denseHeader: props.denseHeader !== undefined ? props.denseHeader : false,
denseBody: props.denseBody !== undefined ? props.denseBody : false,
denseBottom: denseBottom,
aloneGroupRecords: [],
aloneGroupByField: alongGroupByField,
},
store: {
expand: props.treeDefaultExpandAll,
expandDatas: [],
alreadyLoadAllData: false,
lazyloadNoChildrenDatas: [],
headerTicked: false,
dragRecords: [],
inlineEditStatus: Constant.EDIT_STATUS.NONE,
cellSelected: undefined,
mergeGroupRecords: {},
spaceHeight: 4,
noDataLabelI18nKey: 'tip.noData',
loading: false,
loadingLabelI18nKey: 'tip.dataLoading',
bodyCellHeight: bodyCellHeight,
resizeFlag: false,
location: {
yLocation: 0,
noDataTrYLocation: 0,
topHeight: 0,
bottomHeight: 0,
middleWidth: 0,
middleScrollWidth: 0,
middleHeight: 0,
columnHeadHeight: 0,
titleTotalHeight: 0,
hideHeaderNoDataHeight: 0,
tableInFullscreenY: 0,
noDataTrInFullscreenY: 0,
},
pagination: {
config: {
boundaryLinks: true,
boundaryNumbers: false,
directionLinks: false,
ellipses: false,
maxPages: 5,
},
sortBy: '',
descending: false,
reqPageStart: props.pagination['reqPageStart'] || 0,
page: 1,
rowsPerPage: props.pagination['rowsPerPage'] || 10,
rowsNumber: 10,
rowsPerPageOptions: [5, 10, 20, 50, 100],
},
},
});
this.em = new EventManager(props, this.table);
this.cm = new ComputedManager(props, this.table);
this.bm = new ButtonManager(props, this.table);
this.apiFM = new ExposeApiManager(props, this.table);
this.dataFM = new RowData(props, this.table);
this.opFM = new Operator(props, this.table);
this.editFM = new InlineEdit(props, this.table);
this.dndFM = new DragAndDrop(props, this.table);
this.reqApiFM = new RequestApi(props, this.table);
this.criteriaFM = new Criteria(props, this.table);
}
// 设置组件实例
setInstance(instance: any) {
const this_ = this;
this.instance = instance;
this.em.setInstance(instance, this_);
this.cm.setInstance(instance, this_);
this.apiFM.setInstance(instance, this_);
this.bm.setInstance(instance, this_);
this.dataFM.setInstance(instance, this_);
this.opFM.setInstance(instance, this_);
this.editFM.setInstance(instance, this_);
this.dndFM.setInstance(instance, this_);
this.reqApiFM.setInstance(instance, this_);
this.criteriaFM.setInstance(instance, this_);
}
/**
*
* @param target
*/
static removeExtraFields(target: any) {
if (!target) {
console.error('[w-grid] target is undefined.');
return;
}
if (Array.isArray(target)) {
const tmp = [...target];
tmp.forEach((item) => {
GridTools.removeFields(item);
});
return tmp;
} else {
const tmp = { ...target };
GridTools.removeFields(tmp);
return tmp;
}
}
private static removeFields(target: any) {
if (Tools.hasOwnProperty(target, Constant.FIELD_NAMES.ROW_KEY)) {
delete target[Constant.FIELD_NAMES.ROW_KEY];
}
if (Tools.hasOwnProperty(target, Constant.FIELD_NAMES.EXPAND)) {
delete target[Constant.FIELD_NAMES.EXPAND];
}
if (Tools.hasOwnProperty(target, Constant.FIELD_NAMES.TICKED_COUNT)) {
delete target[Constant.FIELD_NAMES.TICKED_COUNT];
}
if (Tools.hasOwnProperty(target, Constant.FIELD_NAMES.CHILDREN_TICKED_COUNT)) {
delete target[Constant.FIELD_NAMES.CHILDREN_TICKED_COUNT];
}
if (Tools.hasOwnProperty(target, Constant.FIELD_NAMES.ROW_OLD_VALUE)) {
delete target[Constant.FIELD_NAMES.ROW_OLD_VALUE];
}
if (Tools.hasOwnProperty(target, Constant.FIELD_NAMES.ROW_INDEX)) {
delete target[Constant.FIELD_NAMES.ROW_INDEX];
}
if (Tools.hasOwnProperty(target, Constant.FIELD_NAMES.LAZYLOAD_NO_CHILDREN)) {
delete target[Constant.FIELD_NAMES.LAZYLOAD_NO_CHILDREN];
}
}
/**
*
* [{id: 1, value: '张三'}, {id: 2, value: '李四'}] ==> { 1: { id: 1, value: '张三' }, 2: { id: 2, value: '李四'} }
* @param array
* @param propertyName
* @returns
*/
static arrayToObject(array: Array<any>, propertyName: string) {
const result = array.reduce((tmp, item) => {
tmp[item[propertyName]] = item;
return tmp;
}, {});
return result;
}
/**
* scope
* @param currentScope scope
* @param child
* @param childIndex
* @param selectedFieldName
* @returns
*/
static buildChildrenScope(currentScope: any, child: any, childIndex: number, selectedFieldName: string, tickedFieldName: string) {
const cols = GridTools.buildChildrenCols(currentScope, child);
const childScope = {
...currentScope,
cols: cols,
key: child[Constant.FIELD_NAMES.ROW_KEY],
[Constant.FIELD_NAMES.EXPAND]: child[Constant.FIELD_NAMES.EXPAND],
pageIndex: childIndex,
rowIndex: childIndex,
row: child,
[selectedFieldName]: child[selectedFieldName],
[tickedFieldName]: child[tickedFieldName],
};
return childScope;
}
// 构建孩子行 scope 中 cols 属性
private static buildChildrenCols(currentScope: any, child: any) {
const cols = <any>[];
currentScope.cols.forEach((col) => {
cols.push({ ...col, value: GridTools.formatValue(child, col) });
});
return cols;
}
// 格式化值
private static formatValue(child: any, col: any) {
if (col.format && typeof col.format === 'function') {
try {
return col.format(child[col.name], child);
} catch (error) {
console.error('[w-grid]column `' + col.name + '` format error:', error);
}
}
return child[col.name];
}
}

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

@ -0,0 +1,226 @@
import { Tools, $t } from '@/platform';
import { Constant } from './index';
/**
*
*/
export class Init {
/**
* props
* @returns
*/
public static createProps() {
return {
height: { type: Number, default: 0 }, // 表格高度
title: { type: String, default: '' }, // 表格标题
autoFetchData: { type: Boolean, default: true }, // 自动加载数据
localMode: { type: Boolean, default: false }, // 本地模式,启用该模式后,除了查询数据依旧通过 autoFetchData 与 url 控制外,其他所有与后端交互的请求都失效,同时分页属性也失效
selectMode: { type: String, default: Constant.SELECT_MODE.ROW }, // 选择模式
dndMode: { type: String, default: undefined }, // 拖拽数据行排序的模式,默认不可拖拽,字符串`local`为本地排序,只根据配置的字段进行本地排序,不会将数据发送至后端,字符串`server`为远程排序,拖拽完成后会将数据发送至后端。
dndOrderBy: { type: String, default: 'order' }, // 拖拽排序时根据哪个字段进行排序,默认为`order`字段
pageable: { type: Boolean, default: true }, // 是否需要分页,当启用树形表格模式时该属性失效,树形表格不支持分页。
configButton: { type: Boolean, default: true }, // 是否显示表格配置按钮
dataUrl: { type: String, default: '' }, // 表格数据操作请求的URL前缀
fetchDataUrl: { type: String, default: '' }, // 获取数据URL
addDataUrl: { type: String, default: '' }, // 新增数据url
editDataUrl: { type: String, default: '' }, // 修改数据url
removeDataUrl: { type: String, default: '' }, // 删除数据url
dense: { type: Boolean, default: undefined }, // 表格整体紧凑模式
denseHeader: { type: Boolean, default: undefined }, // 表格列标题紧凑模式
denseBody: { type: Boolean, default: undefined }, // 表格数据行紧凑模式
denseBottom: { type: Boolean, default: undefined }, // 表格底部分页栏紧凑模式
denseToolbar: { type: Boolean, default: true }, // 表格toolbar按钮栏紧凑模式
sortNo: { type: Boolean, default: false }, // 是否显示排序号
stickyNum: { type: Number, default: 0 }, // 从左侧开始冻结列数,复选框与排序列不计算在内,支持1-10列。
checkboxSelection: { type: Boolean, default: true }, // checkbox选择模式,默认启用
tickedField: { type: String, default: 'ticked' }, // 行数据中记录checkbox勾选状态的字段名
selectedField: { type: String, default: 'selected' }, // 行数据中记录行点击选中状态的字段名
tree: { type: Boolean, default: false }, // 树形表格模式,按照层级关系构建数据并以树形展现
treeLazyLoad: { type: Boolean, default: false }, // 树型表格模式,懒加载
treeIcon: { type: Function, default: undefined }, // 树形表格模式,图标函数,带有一个参数,行数据
treeDefaultExpandAll: { type: Boolean, default: false }, // 树形表格模式,树形表格数据加载后是否全部展开
treeTickStrategy: { type: String, default: 'leaf' }, // 树形表格模式,树勾选策略,strict:节点自己,leaf:包含子节点
treeRelationship: { type: String, default: 'parent' }, // 树形表格模式的数据关系,包括:parent, children,当为parent时组件根据数据主键与数据外键构建树形数据,否则需要自己构建好树形数据放到children属性中。
primaryKey: { type: String, default: 'id' }, // 数据主键(常规表格模式时,该字段可用作内置的编辑删除等功能对应的后端API入参,如:继承RestCrudController的update所需的入参。树形表格模式时,该字段为构建树数据的主键)
foreignKey: { type: String, default: 'parent' }, // 数据外键(常规表格模式时,该字段暂时无用,将来可用作多个表格的数据关系字段。树形表格模式时,该字段为构建树数据的关系字段)
refreshData: { type: Boolean, default: false }, // 新增、删除、修改成功后是否刷新数据列表,默认不刷新但是新增修改后台必须返回对应的行数据对象,删除则必须返回删除的记录集primaryKey集合。
dbClickOperation: { type: String, default: 'view' }, // 默认的双击操作:可填写内置或自定义按钮name,执行的操作为按钮对应的click,固定值提供:expand(展开双击的行)、none(双击不执行任何动作)
separator: { type: String, default: 'cell' }, // 表格分割线,支持:horizontal、vertical、cell、none
hideHeader: { type: Boolean, default: false }, // 隐藏表头
hideBottom: { type: Boolean, default: false }, // 隐藏底部
advancedQuery: { type: Boolean, default: false }, // 高级查询
groupMode: { type: String, default: undefined }, // 分组模式,支持:alone、merge
groupStartOpen: { type: [String, Array, Function], default: Constant.GROUP_START_OPEN.ALL }, // alone分组模式下默认展开的组,字符串支持:all、first、none,数组可配置多个组名。
groupByField: {
// 分组字段配置,当分组模式为 alone 时若配置的为数组,取数组第一个字段。
type: [String, Array],
default: () => {
return [];
},
},
appendRows: {
// 表格追加行,添加到当前表格数据行尾,可添加多行,支持跨行跨列配置,用于添加合计或者额外信息。
type: Array,
default: () => {
return [];
},
},
sortBy: {
type: Array,
default: () => {
return [];
},
}, // 默认排序字段,示例:['userName', '-lastModifyDate'],其中“-”开头表示倒序。
tickedRecord: {
// 默认勾选的记录,传入一个对象,包含:columnName(列名称,字符串类型,该属性不传默认根据数据主键查找)、data(需要勾选的数据,数组类型)
type: Object,
default: () => {
return {};
},
},
queryCriteria: {
// 查询条件,查询、翻页、刷新等操作都会带上的查询条件
type: Object,
default: () => {
return {};
},
},
columns: {
type: Array,
default: () => {
return [];
},
},
queryFormFields: {
type: Array,
default: () => {
return [];
},
},
queryFormColsNum: {
type: [Number, Object],
default: 0,
},
queryFormRowNum: { type: Number, default: 1 },
queryFormAttrs: {
type: Object,
default: () => {
return {};
},
},
toolbarActions: {
type: Array,
default: () => {
return [];
},
},
toolbarConfigure: {
type: Object,
default: () => {
return {};
},
},
pagination: {
type: Object,
default: () => {
return {
reqPageStart: 0,
rowsPerPage: 10,
};
},
},
editor: {
type: Object,
default: () => {
return {
dialog: {},
form: {},
};
},
},
viewer: {
type: Object,
default: () => {
return {
drawer: {},
panel: {
fields: [],
},
};
},
},
// 作为form中的组件使用----start
showIf: {
type: [Boolean, Function],
default: () => {
return true;
},
},
form: {
type: Object,
default: undefined,
},
// 作为form中的组件使用----end
};
}
/**
*
* @param columns
* @param props props
* @returns
*/
public static initColumn(columns: Array<any>, props: any) {
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 });
columns.forEach((item: any) => {
this.childrenHandler(item, gridColumns, sortable);
});
return gridColumns;
}
return [];
}
// 孩子列处理
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);
});
} else {
this.columnStyle(item);
const col = {
...{ align: 'left', label: item.name, field: item.name, name: item.name, sortable: sortable, showIf: true },
...item,
};
if (Tools.isEmpty(col.name)) {
col.name = Tools.uuid();
}
gridColumns.push(col);
}
}
// 列样式处理
private static columnStyle(item: any) {
let style = '';
if (Tools.hasOwnProperty(item, 'style')) {
style = item.style;
}
if (Tools.hasOwnProperty(item, 'width')) {
if (typeof item.width === 'number') {
item.style = `min-width: ` + item.width + `px; width: ` + item.width + `px;max-width: ` + item.width + `px;` + style;
} else {
item.style = `min-width: ` + item.width + `; width: ` + item.width + `;max-width: ` + item.width + `;` + style;
}
delete item.width;
if (Tools.hasOwnProperty(item, 'classes')) {
item.classes = item.classes + ' truncate';
} else {
item.classes = 'truncate';
}
}
}
}

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

@ -0,0 +1,252 @@
import { Tools } from '@/platform';
import { computed } from 'vue';
import { Base } from '../Base';
import { Constant } from '../constant/Constant';
import { GridTools } from '../GridTools';
/**
* w-grid
*
*/
export class ComputedManager extends Base {
/**
*
*/
denseHeader = computed(() => {
if (this.table?.configStore?.denseHeader) {
return true;
} else if (this.table?.configStore.dense !== false) {
return true;
} else {
return false;
}
});
/**
*
*/
denseBody = computed(() => {
if (this.table?.configStore?.denseBody) {
return true;
} else if (this.table?.configStore.dense !== false) {
return true;
} else {
return false;
}
});
/**
*
*/
denseBottom = computed(() => {
if (this.table?.configStore?.denseBottom) {
return true;
} else if (this.table?.configStore.dense !== false) {
return true;
} else {
return false;
}
});
/**
*
*/
displayColumns = computed(() => {
const visibleColumns: string[] = [];
this.table?.columns.forEach((item) => {
if (!this.props.tree || (this.props.tree && item.name !== Constant.FIELD_NAMES.SORT_NO)) {
if (Tools.isEmpty(item.showIf)) {
visibleColumns.push(item.name);
} else if (typeof item.showIf === 'boolean' && item.showIf) {
visibleColumns.push(item.name);
}
}
});
return visibleColumns;
});
/**
*
*/
mergeGroupByField = computed(() => {
if (typeof this.props.groupByField === 'string') {
[this.props.groupByField];
}
return this.props.groupByField;
});
/**
* checkbox复选框
*/
extColumnNum = computed(() => {
let num = 0;
if (this.props.tree) {
return num;
}
if (this.table?.configStore.showSortNo) {
num += 1;
}
if (this.table?.configStore.useCheckboxSelection) {
num += 1;
}
return num;
});
/**
* checkbox复选框
*/
displayColumnNum = computed(() => {
let colspan = this.extColumnNum.value;
colspan += this.displayColumns.value.length;
return colspan;
});
/**
*
*/
noDataStyle = computed(() => {
let style = {};
if (this.props.hideHeader) {
style = {
height: this.table?.store.location.hideHeaderNoDataHeight + 'px',
display: 'flex',
'align-items': 'center',
'justify-content': 'center',
};
} else {
let availableHeight = 0;
let otherHeight = 0;
let otherHeight2 = 0;
const parentHtmlElement = this.instance?.getHtmlElement()?.parentElement;
if (parentHtmlElement) {
availableHeight = Math.floor(parentHtmlElement.clientHeight);
}
otherHeight += this.table?.store.location.topHeight || 0;
otherHeight += this.table?.store.location.bottomHeight || 0;
otherHeight += this.table?.store.spaceHeight || 0;
otherHeight2 += this.table?.store.location.titleTotalHeight;
otherHeight2 += this.table?.store.location.middleScrollWidth - this.table?.store.location.middleWidth > 0 ? 15 : 0;
availableHeight -= otherHeight + otherHeight2;
availableHeight -= 2; //无数据增加的title行,下边框所占边框
style = {
height: this.props.height > 0 ? this.props.height - otherHeight2 - 1 + 'px' : availableHeight > 0 ? availableHeight + 'px' : '0px',
};
}
if (this.table?.store.resizeFlag) {
return style;
}
return style;
});
/**
*
*/
actionArgs = computed(() => {
return {
selected: this.tools?.apiFM.getData.getSelectedRow(),
selecteds: this.tools?.apiFM.getData.getSelectedRows(),
ticked: this.tools?.apiFM.getData.getTickedRow(),
tickeds: this.tools?.apiFM.getData.getTickedRows(),
grid: this.instance,
selectedColName: this.table.store.cellSelected?.colName,
};
});
/**
*
*/
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) {
// 存在多行列头
const stickyColumn = this.table.originalColumns.filter((item, index) => {
return index < this.table.configStore.stickyNum;
});
let tdNum = this.extColumnNum.value;
stickyColumn.forEach((item: any, index: number) => {
tdNum += this.table.componentRef.getHeaderRef().getMoreColumnTitleMap()?.get(item.name)?.colspan;
});
// 处理表格数据列锁定
if (tdNum > 0) {
for (let n = 1; n <= tdNum; n++) {
classArr.push('sticky-header-column-table-td-' + n);
}
}
} else {
// 不存在多行列头
if (this.extColumnNum.value === 2) {
classArr.push('sticky-header-column-table-tr-1' + '-' + 1);
classArr.push('sticky-header-column-table-tr-1' + '-' + 2);
classArr.push('sticky-header-column-table-td-' + 1);
classArr.push('sticky-header-column-table-td-' + 2);
} else if (this.extColumnNum.value === 1) {
classArr.push('sticky-header-column-table-tr-1' + '-' + 1);
classArr.push('sticky-header-column-table-td-' + 1);
}
for (let i = 1; i <= this.table.configStore.stickyNum; i++) {
classArr.push('sticky-header-column-table-tr-1' + '-' + (i + this.extColumnNum.value));
classArr.push('sticky-header-column-table-td-' + (i + this.extColumnNum.value));
}
}
}
return classArr;
});
/**
*
*/
tableHeight = computed(() => {
let availableHeight = 0;
const parentHtmlElement = this.instance?.getHtmlElement()?.parentElement;
if (parentHtmlElement) {
availableHeight = Math.floor(parentHtmlElement.clientHeight);
}
availableHeight -= this.table?.store.location.topHeight || 0;
availableHeight -= this.table?.store.location.bottomHeight || 0;
availableHeight -= this.table?.store.spaceHeight || 0;
const style = {
height: this.props.height > 0 ? this.props.height + 'px' : availableHeight > 0 ? availableHeight + 'px' : '0px',
};
if (this.table?.store.resizeFlag) {
return style;
}
return style;
});
/**
*
*/
rowsPerPageOptions = computed(() => {
if (this.props.pageable && !this.props.localMode && !this.props.tree && this.table.store.location.middleWidth > 600) {
return this.table.store.pagination.rowsPerPageOptions;
}
return [];
});
/**
* form中的字段使用时是否显示
*/
showIf = computed(() => {
if (typeof this.props.showIf === 'function') {
return this.props.showIf({
value: null,
form: this.props.form,
});
} else {
return this.props.showIf;
}
});
setInstance(instance: any, tools?: GridTools): void {
this.instance = instance;
this.tools = tools;
}
}

58
io.sc.platform.core.frontend/src/platform/components/grid/ts/constant/Constant.ts

@ -0,0 +1,58 @@
import { SelectMode } from './src/SelectMode';
import { DndMode } from './src/DndMode';
import { GroupMode } from './src/GroupMode';
import { GroupStartOpen } from './src/GroupStartOpen';
import { FormStatus } from './src/FormStatus';
import { EditStatus } from './src/EditStatus';
import { FieldNames } from './src/FieldNames';
import { CriteriaOperator } from './src/CriteriaOperator';
/**
* w-grid
*/
export class Constant {
/**
*
*/
static SELECT_MODE = SelectMode;
/**
*
*/
static DND_MODE = DndMode;
/**
*
*/
static DND_BG_IMAGE = `data:image/svg+xml,%3Csvg %3E%3Cpath /%3E%3C/svg%3E`;
/**
*
*/
static GROUP_MODE = GroupMode;
/**
*
*/
static GROUP_START_OPEN = GroupStartOpen;
/**
* Form表单状态
*/
static FORM_STATUS = FormStatus;
/**
*
*/
static EDIT_STATUS = EditStatus;
/**
*
*/
static FIELD_NAMES = FieldNames;
/**
* criteria查询支持的操作
*/
static CRITERIA_OPERATOR = CriteriaOperator;
}

85
io.sc.platform.core.frontend/src/platform/components/grid/ts/constant/src/CriteriaOperator.ts

@ -0,0 +1,85 @@
/**
* criteria查询支持的操作
*/
export class CriteriaOperator {
/**
* value is null or value=''
*/
static isBlank = 'isBlank';
/**
* value is not null && value<>''
*/
static notBlank = 'notBlank';
/**
* value is null
*/
static isNull = 'isNull';
/**
* value is not null
*/
static notNull = 'notNull';
/**
* =
*/
static equals = 'equals';
/**
* <>
*/
static notEqual = 'notEqual';
/**
* >
*/
static greaterThan = 'greaterThan';
/**
* >=
*/
static greaterOrEqual = 'greaterOrEqual';
/**
* <
*/
static lessThan = 'lessThan';
/**
* <=
*/
static lessOrEqual = 'lessOrEqual';
/**
* like %xxx%
*/
static contains = 'contains';
/**
* not like %xxx%
*/
static notContains = 'notContains';
/**
* like xxx%
*/
static startsWith = 'startsWith';
/**
* not like xxx%
*/
static notStartsWith = 'notStartsWith';
/**
* like %xxx
*/
static endsWith = 'endsWith';
/**
* not like %xxx
*/
static notEndsWith = 'notEndsWith';
/**
* min<x and x<max
*/
static between = 'between';
/**
* min<=x and x<=max
*/
static betweenInclusive = 'betweenInclusive';
/**
* in ()
*/
static inSet = 'inSet';
/**
* not in ()
*/
static notInSet = 'notInSet';
}

24
io.sc.platform.core.frontend/src/platform/components/grid/ts/constant/src/DndMode.ts

@ -0,0 +1,24 @@
/**
*
*/
export class DndMode {
/**
*
*/
public static LOCAL = 'local';
/**
*
*/
public static SERVER = 'server';
/**
*
* @returns
*/
public static getAll() {
return {
[DndMode.LOCAL]: DndMode.LOCAL,
[DndMode.SERVER]: DndMode.SERVER,
};
}
}

34
io.sc.platform.core.frontend/src/platform/components/grid/ts/constant/src/EditStatus.ts

@ -0,0 +1,34 @@
/**
*
*/
export class EditStatus {
/**
*
*/
public static NONE = 'none';
/**
*
*/
public static CELL = 'cellEdit';
/**
*
*/
public static ROW = 'rowEdit';
/**
*
*/
public static ROWS = 'rowsEdit';
/**
*
* @returns
*/
public static getAll() {
return {
[EditStatus.NONE]: EditStatus.NONE,
[EditStatus.CELL]: EditStatus.CELL,
[EditStatus.ROW]: EditStatus.ROW,
[EditStatus.ROWS]: EditStatus.ROWS,
};
}
}

63
io.sc.platform.core.frontend/src/platform/components/grid/ts/constant/src/FieldNames.ts

@ -0,0 +1,63 @@
/**
*
*/
export class FieldNames {
/**
*
*/
public static ROW_KEY = '_rowKey_';
/**
*
*/
public static SORT_NO = '_sortNo_';
/**
*
*/
public static EXPAND = 'expand';
/**
* 01
*/
public static TICKED_COUNT = '_tickedCount';
/**
*
*/
public static CHILDREN_TICKED_COUNT = '_childrenTickedCount';
/**
*
*/
public static ROW_OLD_VALUE = '_rowOldValue';
/**
*
*/
public static ROW_INDEX = '_rowIndex';
/**
*
*/
public static LAZYLOAD_NO_CHILDREN = '_lazyloadNoChildren';
/**
*
* @param props
* @returns
*/
public static getExtraFields(props: object) {
return {
rowKey: FieldNames.ROW_KEY,
ticked: props['tickedField'], // 行数据勾选状态
selected: props['selectedField'], // 行数据选中状态
expand: FieldNames.EXPAND,
tickedCount: FieldNames.TICKED_COUNT,
childrenTickedCount: FieldNames.CHILDREN_TICKED_COUNT,
rowOldValue: FieldNames.ROW_OLD_VALUE,
rowIndex: FieldNames.ROW_INDEX,
lazyloadNoChildren: FieldNames.LAZYLOAD_NO_CHILDREN,
};
}
}

39
io.sc.platform.core.frontend/src/platform/components/grid/ts/constant/src/FormStatus.ts

@ -0,0 +1,39 @@
/**
* Form表单状态
*/
export class FormStatus {
/**
*
*/
public static ADD = 'add';
/**
*
*/
public static EDIT = 'edit';
/**
*
*/
public static CLONE = 'clone';
/**
*
*/
public static ADD_TOP = 'addTop';
/**
*
*/
public static ADD_CHILD = 'addChild';
/**
* Form表单状态对象
* @returns
*/
public static getAll() {
return {
[FormStatus.ADD]: FormStatus.ADD,
[FormStatus.EDIT]: FormStatus.EDIT,
[FormStatus.CLONE]: FormStatus.CLONE,
[FormStatus.ADD_TOP]: FormStatus.ADD_TOP,
[FormStatus.ADD_CHILD]: FormStatus.ADD_CHILD,
};
}
}

24
io.sc.platform.core.frontend/src/platform/components/grid/ts/constant/src/GroupMode.ts

@ -0,0 +1,24 @@
/**
*
*/
export class GroupMode {
/**
*
*/
public static ALONE = 'alone';
/**
*
*/
public static MERGE = 'merge';
/**
*
* @returns
*/
public static getAll() {
return {
[GroupMode.ALONE]: GroupMode.ALONE,
[GroupMode.MERGE]: GroupMode.MERGE,
};
}
}

29
io.sc.platform.core.frontend/src/platform/components/grid/ts/constant/src/GroupStartOpen.ts

@ -0,0 +1,29 @@
/**
*
*/
export class GroupStartOpen {
/**
*
*/
public static ALL = 'all';
/**
*
*/
public static FIRST = 'first';
/**
*
*/
public static NONE = 'none';
/**
*
* @returns
*/
public static getAll() {
return {
[GroupStartOpen.ALL]: GroupStartOpen.ALL,
[GroupStartOpen.FIRST]: GroupStartOpen.FIRST,
[GroupStartOpen.NONE]: GroupStartOpen.NONE,
};
}
}

34
io.sc.platform.core.frontend/src/platform/components/grid/ts/constant/src/SelectMode.ts

@ -0,0 +1,34 @@
/**
*
*/
export class SelectMode {
/**
*
*/
public static NONE = 'none';
/**
*
*/
public static SINGLE_ROW = 'singleRow';
/**
*
*/
public static ROW = 'row';
/**
*
*/
public static CELL = 'cell';
/**
*
* @returns
*/
public static getAll() {
return {
[SelectMode.NONE]: SelectMode.NONE,
[SelectMode.SINGLE_ROW]: SelectMode.SINGLE_ROW,
[SelectMode.ROW]: SelectMode.ROW,
[SelectMode.CELL]: SelectMode.CELL,
};
}
}

175
io.sc.platform.core.frontend/src/platform/components/grid/ts/event/EventManager.ts

@ -0,0 +1,175 @@
import type { PropsType } from '../types/PropsType';
import { RowClick } from './src/RowClick';
import { RowDbClick } from './src/RowDbClick';
import { AfterDragAndDrop } from './src/AfterDragAndDrop';
import { UpdateTicked } from './src/UpdateTicked';
import { UpdateTickeds } from './src/UpdateTickeds';
import { BeforeRequestData } from './src/BeforeRequestData';
import { AfterRequestData } from './src/AfterRequestData';
import { BeforeEditorDataSubmit } from './src/BeforeEditorDataSubmit';
import { AfterEditorDataSubmit } from './src/AfterEditorDataSubmit';
import { BeforeRemove } from './src/BeforeRemove';
import { AfterRemove } from './src/AfterRemove';
import { AfterEditorOpen } from './src/AfterEditorOpen';
import { GridTools } from '../GridTools';
import { TableType } from '../types/TableType';
/**
* w-grid
*/
export class EventManager {
private _rowClick: RowClick;
private _rowDbClick: RowDbClick;
private _aftetDnd: AfterDragAndDrop;
private _updateTicked: UpdateTicked;
private _updateTickeds: UpdateTickeds;
private _beforeRequestData: BeforeRequestData;
private _afterRequestData: AfterRequestData;
private _beforeEditorDataSubmit: BeforeEditorDataSubmit;
private _afterEditorDataSubmit: AfterEditorDataSubmit;
private _beforeRemove: BeforeRemove;
private _afterRemove: AfterRemove;
private _afterEditorOpen: AfterEditorOpen;
constructor(props: PropsType, table: TableType) {
this._rowClick = new RowClick(props, table);
this._rowDbClick = new RowDbClick(props, table);
this._aftetDnd = new AfterDragAndDrop(props, table);
this._updateTicked = new UpdateTicked(props, table);
this._updateTickeds = new UpdateTickeds(props, table);
this._beforeRequestData = new BeforeRequestData(props, table);
this._afterRequestData = new AfterRequestData(props, table);
this._beforeEditorDataSubmit = new BeforeEditorDataSubmit(props, table);
this._afterEditorDataSubmit = new AfterEditorDataSubmit(props, table);
this._beforeRemove = new BeforeRemove(props, table);
this._afterRemove = new AfterRemove(props, table);
this._afterEditorOpen = new AfterEditorOpen(props, table);
}
/**
*
* @param evt html事件对象
* @param row
* @param index
*/
public rowClick(evt: any, row: any, index: number) {
this._rowClick.execute(evt, row, index);
}
/**
*
* @param evt html事件对象
* @param row
* @param index
*/
public rowDbClick(evt: any, row: any, index: number) {
this._rowDbClick.execute(evt, row, index);
}
/**
*
* @param updateData
*/
public afterDragAndDrop(updateData: any) {
this._aftetDnd.execute(updateData);
}
/**
* checkbox勾选状态改变事件
* @param evt html事件对象
* @param row
*/
public updateTicked(evt: any, row: any) {
this._updateTicked.execute(evt, row);
}
/**
* checkbox全部勾选状态改变事件
* @param value
*/
public updateTickeds(value: boolean | null) {
this._updateTickeds.execute(value);
}
/**
*
* @param requestParams
* @returns
*/
public async beforeRequestData(requestParams: any) {
return await this._beforeRequestData.execute(requestParams);
}
/**
*
* @param rows
*/
public afterRequestData(rows: any) {
this._afterRequestData.execute(rows);
}
/**
*
* @param data
* @returns {
* data: data, //原始数据或被修改后的数据
* submit: true,//是否提交
* closeDialog: true,//提交成功后是否关闭窗口
* }
*/
public beforeEditorDataSubmit(data: any): any {
return this._beforeEditorDataSubmit.execute(data);
}
/**
*
* @param data
*/
public afterEditorDataSubmit(data: any) {
this._afterEditorDataSubmit.execute(data);
}
/**
*
* @param ids id集合
* @returns {
* submit: true,//是否提交
* ids: <any> [],//被修改后的id集合
* }
*/
public async beforeRemove(ids: any) {
return await this._beforeRemove.execute(ids);
}
/**
*
* @param ids id集合
*/
public afterRemove(ids: any) {
this._afterRemove.execute(ids);
}
/**
*
* @param data
*/
public afterEditorOpen(data: any) {
this._afterEditorOpen.execute(data);
}
setInstance(instance: any, tools: GridTools) {
this._rowClick.setInstance(instance, tools);
this._rowDbClick.setInstance(instance, tools);
this._aftetDnd.setInstance(instance, tools);
this._updateTicked.setInstance(instance, tools);
this._updateTickeds.setInstance(instance, tools);
this._beforeRequestData.setInstance(instance, tools);
this._afterRequestData.setInstance(instance, tools);
this._beforeEditorDataSubmit.setInstance(instance, tools);
this._afterEditorDataSubmit.setInstance(instance, tools);
this._beforeRemove.setInstance(instance, tools);
this._afterRemove.setInstance(instance, tools);
this._afterEditorOpen.setInstance(instance, tools);
}
}

18
io.sc.platform.core.frontend/src/platform/components/grid/ts/event/src/AfterDragAndDrop.ts

@ -0,0 +1,18 @@
import { Base } from '../../Base';
import { PropsType, TableType } from '../../index';
/**
* w-grid
*/
export class AfterDragAndDrop extends Base {
constructor(props: PropsType, table: TableType) {
super(props, table);
this.execute = this.execute.bind(this);
}
static NAME = 'afterDragAndDrop';
public execute(updateData: any) {
this.instance.emit(AfterDragAndDrop.NAME, { grid: this.instance, data: updateData });
}
}

18
io.sc.platform.core.frontend/src/platform/components/grid/ts/event/src/AfterEditorDataSubmit.ts

@ -0,0 +1,18 @@
import { Base } from '../../Base';
import { PropsType, TableType } from '../../index';
/**
* w-grid
*/
export class AfterEditorDataSubmit extends Base {
static NAME = 'afterEditorDataSubmit';
constructor(props: PropsType, table: TableType) {
super(props, table);
this.execute = this.execute.bind(this);
}
public execute(data: any) {
this.instance.emit(AfterEditorDataSubmit.NAME, { grid: this.instance, data });
}
}

18
io.sc.platform.core.frontend/src/platform/components/grid/ts/event/src/AfterEditorOpen.ts

@ -0,0 +1,18 @@
import { Base } from '../../Base';
import { PropsType, TableType } from '../../index';
/**
* w-grid
*/
export class AfterEditorOpen extends Base {
static NAME = 'afterEditorOpen';
constructor(props: PropsType, table: TableType) {
super(props, table);
this.execute = this.execute.bind(this);
}
public execute(data: any) {
this.instance.emit(AfterEditorOpen.NAME, { grid: this.instance, data });
}
}

18
io.sc.platform.core.frontend/src/platform/components/grid/ts/event/src/AfterRemove.ts

@ -0,0 +1,18 @@
import { Base } from '../../Base';
import { PropsType, TableType } from '../../index';
/**
* w-grid
*/
export class AfterRemove extends Base {
static NAME = 'afterRemove';
constructor(props: PropsType, table: TableType) {
super(props, table);
this.execute = this.execute.bind(this);
}
public execute(ids: any) {
this.instance.emit(AfterRemove.NAME, { grid: this.instance, ids });
}
}

18
io.sc.platform.core.frontend/src/platform/components/grid/ts/event/src/AfterRequestData.ts

@ -0,0 +1,18 @@
import { Base } from '../../Base';
import { PropsType, TableType } from '../../index';
/**
* w-grid
*/
export class AfterRequestData extends Base {
static NAME = 'afterRequestData';
constructor(props: PropsType, table: TableType) {
super(props, table);
this.execute = this.execute.bind(this);
}
public execute(rows: any) {
this.instance.emit(AfterRequestData.NAME, { grid: this.instance, rows });
}
}

35
io.sc.platform.core.frontend/src/platform/components/grid/ts/event/src/BeforeEditorDataSubmit.ts

@ -0,0 +1,35 @@
import { Base } from '../../Base';
import { PropsType, TableType } from '../../index';
/**
* w-grid
*/
export class BeforeEditorDataSubmit extends Base {
static NAME = 'beforeEditorDataSubmit';
constructor(props: PropsType, table: TableType) {
super(props, table);
this.execute = this.execute.bind(this);
}
public execute(data: any): any {
const result = {
data: data,
submit: true,
closeDialog: true,
};
this.instance.emit(BeforeEditorDataSubmit.NAME, {
grid: this.instance,
data: data,
callback: (_data: any, closeFlag: boolean = true) => {
if (typeof _data === 'boolean' && _data === false) {
result.submit = false;
} else {
result.data = _data;
}
result.closeDialog = closeFlag;
},
});
return result;
}
}

33
io.sc.platform.core.frontend/src/platform/components/grid/ts/event/src/BeforeRemove.ts

@ -0,0 +1,33 @@
import { Base } from '../../Base';
import { PropsType, TableType } from '../../index';
/**
* w-grid
*/
export class BeforeRemove extends Base {
static NAME = 'beforeRemove';
constructor(props: PropsType, table: TableType) {
super(props, table);
this.execute = this.execute.bind(this);
}
public async execute(ids: any) {
const result = {
submit: true,
ids: <any>[],
};
await this.instance.emit(BeforeRemove.NAME, {
grid: this.instance,
ids: ids,
callback: (_ids: any) => {
if (_ids && Array.isArray(_ids)) {
result.ids.push(..._ids);
} else {
result.submit = false;
}
},
});
return result;
}
}

25
io.sc.platform.core.frontend/src/platform/components/grid/ts/event/src/BeforeRequestData.ts

@ -0,0 +1,25 @@
import { Base } from '../../Base';
import { PropsType, TableType } from '../../index';
/**
* w-grid
*/
export class BeforeRequestData extends Base {
static NAME = 'beforeRequestData';
constructor(props: PropsType, table: TableType) {
super(props, table);
this.execute = this.execute.bind(this);
}
public async execute(requestParams: any) {
let result = undefined;
await this.instance.emit(BeforeRequestData.NAME, {
grid: this.instance,
data: requestParams,
callback: (_requestParams: URLSearchParams | any) => {
result = _requestParams;
},
});
return result;
}
}

61
io.sc.platform.core.frontend/src/platform/components/grid/ts/event/src/RowClick.ts

@ -0,0 +1,61 @@
import { Base } from '../../Base';
import { Constant } from '../../constant/Constant';
import { PropsType, TableType } from '../../index';
/**
* w-grid
*/
export class RowClick extends Base {
static NAME = 'rowClick';
constructor(props: PropsType, table: TableType) {
super(props, table);
this.execute = this.execute.bind(this);
this.handleRowClick = this.handleRowClick.bind(this);
this.processRowSelection = this.processRowSelection.bind(this);
this.clickExitInlineEdit = this.clickExitInlineEdit.bind(this);
this.execClick = this.execClick.bind(this);
}
public execute(evt: any, row: any, index: number) {
if (!row) {
return;
}
const selected = this.instance.getSelectedRow();
this.handleRowClick(evt, row, index, selected);
}
private handleRowClick(evt: any, row: any, index: number, selected: any) {
if (this.clickExitInlineEdit(row, selected)) {
this.tools?.editFM.exitInlineEdit();
return;
}
if (this.execClick()) {
this.processRowSelection(evt, row, index);
}
}
private processRowSelection(evt: any, row: any, index: number) {
if (!evt.ctrlKey) {
this.instance.clearSelected();
if (!this.props.tree) {
this.instance.clearTicked();
}
}
row[this.props.selectedField] = true;
if (!this.props.tree) {
row[this.props.tickedField] = true;
this.tools?.opFM.resetHeaderCheckbox();
}
this.instance.emit(RowClick.NAME, { grid: this.instance, evt, row, index });
}
private clickExitInlineEdit(row: any, selected: any) {
return (
this.props.localMode &&
this.table?.store.inlineEditStatus !== Constant.EDIT_STATUS.NONE &&
this.props.selectMode === Constant.SELECT_MODE.ROW &&
row[Constant.FIELD_NAMES.ROW_KEY] !== selected[Constant.FIELD_NAMES.ROW_KEY]
);
}
private execClick() {
return this.table?.store.inlineEditStatus === Constant.EDIT_STATUS.NONE && this.props.selectMode !== Constant.SELECT_MODE.NONE;
}
}

27
io.sc.platform.core.frontend/src/platform/components/grid/ts/event/src/RowDbClick.ts

@ -0,0 +1,27 @@
import { Constant } from '../../constant/Constant';
import { Base } from '../../Base';
import { PropsType, TableType } from '../../index';
/**
* w-grid
*/
export class RowDbClick extends Base {
static NAME = 'rowDbClick';
constructor(props: PropsType, table: TableType) {
super(props, table);
this.execute = this.execute.bind(this);
}
public execute(evt: any, row: any, index: number) {
if (this.table?.store.inlineEditStatus === Constant.EDIT_STATUS.NONE && this.props.selectMode !== Constant.SELECT_MODE.NONE) {
if (this.props.onRowDbClick) {
this.instance.emit(RowDbClick.NAME, { grid: this.instance, evt, row, index });
} else if (this.props.dbClickOperation === 'view') {
this.tools?.apiFM.operator.viewData();
} else if (this.props.dbClickOperation !== 'none') {
this.table.componentRef.getTopRef()?.dbClickOperation(row);
}
}
}
}

33
io.sc.platform.core.frontend/src/platform/components/grid/ts/event/src/UpdateTicked.ts

@ -0,0 +1,33 @@
import { Constant } from '../../constant/Constant';
import { Base } from '../../Base';
import { PropsType, TableType } from '../../index';
/**
* w-grid checkbox勾选状态改变事件
*/
export class UpdateTicked extends Base {
static NAME = 'updateTicked';
constructor(props: PropsType, table: TableType) {
super(props, table);
this.execute = this.execute.bind(this);
}
public execute(evt: Event, row: any) {
const selectedField = this.props.selectedField;
const tickedField = this.props.tickedField;
if (this.table?.store.inlineEditStatus === Constant.EDIT_STATUS.NONE) {
row[selectedField] = row[tickedField];
this.tools?.opFM.resetHeaderCheckbox();
if (!row[tickedField]) {
// 取消选中时将选中的单元格也清空
this.table.store.cellSelected = {};
}
if (this.props.onUpdateTicked) {
this.instance.emit(UpdateTicked.NAME, { grid: this.instance, evt, row });
}
} else {
row[tickedField] = !row[tickedField];
}
}
}

38
io.sc.platform.core.frontend/src/platform/components/grid/ts/event/src/UpdateTickeds.ts

@ -0,0 +1,38 @@
import { Constant } from '../../constant/Constant';
import { Base } from '../../Base';
import { PropsType, TableType } from '../../index';
/**
* w-grid checkbox状态改变事件
*/
export class UpdateTickeds extends Base {
static NAME = 'updateTickeds';
constructor(props: PropsType, table: TableType) {
super(props, table);
this.execute = this.execute.bind(this);
}
public execute(value: boolean | null) {
const selectedField = this.props.selectedField;
const tickedField = this.props.tickedField;
if (this.table?.store.inlineEditStatus === Constant.EDIT_STATUS.NONE) {
if (value) {
this.table.rows.forEach((item) => {
item[tickedField] = true;
item[selectedField] = true;
});
} else {
this.table.rows.forEach((item) => {
item[tickedField] = false;
item[selectedField] = false;
});
}
} else if (this.table?.store.inlineEditStatus === Constant.EDIT_STATUS.ROW || this.table?.store.inlineEditStatus === Constant.EDIT_STATUS.CELL) {
this.table.store.headerTicked = null;
} else if (this.table?.store.inlineEditStatus === Constant.EDIT_STATUS.ROWS) {
this.table.store.headerTicked = false;
}
this.instance.emit(UpdateTickeds.NAME, { grid: this.instance, value: value });
}
}

134
io.sc.platform.core.frontend/src/platform/components/grid/ts/expose-api/ExposeApiManager.ts

@ -0,0 +1,134 @@
import { PropsType } from '../types/PropsType';
import { GridTools } from '../GridTools';
import { Button } from './src/Button';
import { ComponentRef } from './src/ComponentRef';
import { GetData } from './src/GetData';
import { LocalMode } from './src/LocalMode';
import { Operator } from './src/Operator';
import { ResetProperty } from './src/ResetProperty';
import { TableType } from '../types/TableType';
/**
* w-grid API管理器
*/
export class ExposeApiManager {
/**
* API
*/
getData: GetData;
/**
* ref相关的API
*/
componentRef: ComponentRef;
/**
* API
*/
operator: Operator;
/**
* API
*/
localMode: LocalMode;
/**
* API
*/
resetProperty: ResetProperty;
/**
* API
*/
button: Button;
constructor(props: PropsType, table: TableType) {
this.getData = new GetData(props, table);
this.componentRef = new ComponentRef(props, table);
this.operator = new Operator(props, table);
this.localMode = new LocalMode(props, table);
this.resetProperty = new ResetProperty(props, table);
this.button = new Button(props, table);
}
/**
* 使API
* @param tools
* @returns
*/
static createExpose(tools: GridTools) {
return {
// 获取数据api
getRows: tools.apiFM.getData.getRows,
getSelectedRow: tools.apiFM.getData.getSelectedRow,
getSelectedRows: tools.apiFM.getData.getSelectedRows,
getTickedRow: tools.apiFM.getData.getTickedRow,
getTickedRows: tools.apiFM.getData.getTickedRows,
getCascadeChildren: tools.apiFM.getData.getCascadeChildren,
getCascadeParents: tools.apiFM.getData.getCascadeParents,
getSelectedCell: tools.apiFM.getData.getSelectedCell,
// 获取子组件ref的api
getQueryForm: tools.apiFM.componentRef.getQueryForm,
getEditorDialog: tools.apiFM.componentRef.getEditorDialog,
getCellEditorDialog: tools.apiFM.componentRef.getCellEditorDialog,
getEditorForm: tools.apiFM.componentRef.getEditorForm,
getCellEditorForm: tools.apiFM.componentRef.getCellEditorForm,
getViewerDrawer: tools.apiFM.componentRef.getViewerDrawer,
getViewerPanel: tools.apiFM.componentRef.getViewerPanel,
// 表格操作api
setSelected: tools.apiFM.operator.setSelected,
setTicked: tools.apiFM.operator.setTicked,
clearSelected: tools.apiFM.operator.clearSelected,
clearTicked: tools.apiFM.operator.clearTicked,
expand: tools.apiFM.operator.expand,
collapse: tools.apiFM.operator.collapse,
// 本地模式api
setLocalData: tools.apiFM.localMode.setLocalData,
addLocalData: tools.apiFM.localMode.addLocalData,
updateLocalData: tools.apiFM.localMode.updateLocalData,
removeLocalData: tools.apiFM.localMode.removeLocalData,
// 重新设置或覆盖现有表格属性配置api
setQueryCriteria: tools.apiFM.resetProperty.setQueryCriteria,
setQueryCriteriaFieldValue: tools.apiFM.resetProperty.setQueryCriteriaFieldValue,
setDataUrl: tools.apiFM.resetProperty.setDataUrl,
setFetchDataUrl: tools.apiFM.resetProperty.setFetchDataUrl,
setAddDataUrl: tools.apiFM.resetProperty.setAddDataUrl,
setEditDataUrl: tools.apiFM.resetProperty.setEditDataUrl,
setRemoveDataUrl: tools.apiFM.resetProperty.setRemoveDataUrl,
// 内置按钮api
query: tools.apiFM.button.query,
moreQuery: tools.apiFM.button.moreQuery,
reset: tools.apiFM.button.reset,
refresh: tools.apiFM.button.refresh,
add: tools.apiFM.button.add,
addTop: tools.apiFM.button.addTop,
addChild: tools.apiFM.button.addChild,
edit: tools.apiFM.button.edit,
cellEdit: tools.apiFM.button.cellEdit,
inlineCellEdit: tools.apiFM.button.inlineCellEdit,
inlineRowEdit: tools.apiFM.button.inlineRowEdit,
inlineRowsEdit: tools.apiFM.button.inlineRowsEdit,
clone: tools.apiFM.button.clone,
remove: tools.apiFM.button.remove,
view: tools.apiFM.button.view,
export: tools.apiFM.button.exportData,
resetDefaultValues: tools.apiFM.button.resetDefaultValues,
// =========其他API=========
refreshStyle: tools.opFM.resetStyleVariableValue,
getHtmlElement: () => {
return tools.table.componentRef.getTableRef()?.$el;
},
};
}
// 设置组件实例
setInstance(instance: any, tools: GridTools) {
this.getData.setInstance(instance, tools);
this.componentRef.setInstance(instance, tools);
this.operator.setInstance(instance, tools);
this.localMode.setInstance(instance, tools);
this.resetProperty.setInstance(instance, tools);
}
}

133
io.sc.platform.core.frontend/src/platform/components/grid/ts/expose-api/src/Button.ts

@ -0,0 +1,133 @@
import { Tools } from '@/platform';
import { toRaw } from 'vue';
import { Base } from '../../Base';
import { Constant, PropsType, TableType } from '../../index';
/**
* w-grid API
*/
export class Button extends Base {
constructor(props: PropsType, table: TableType) {
super(props, table);
this.getTopRef = this.getTopRef.bind(this);
this.getActionArgs = this.getActionArgs.bind(this);
this.query = this.query.bind(this);
this.moreQuery = this.moreQuery.bind(this);
this.reset = this.reset.bind(this);
this.refresh = this.refresh.bind(this);
this.add = this.add.bind(this);
this.addTop = this.addTop.bind(this);
this.addChild = this.addChild.bind(this);
this.edit = this.edit.bind(this);
this.cellEdit = this.cellEdit.bind(this);
this.inlineCellEdit = this.inlineCellEdit.bind(this);
this.inlineRowEdit = this.inlineRowEdit.bind(this);
this.inlineRowsEdit = this.inlineRowsEdit.bind(this);
this.clone = this.clone.bind(this);
this.remove = this.remove.bind(this);
this.view = this.view.bind(this);
this.exportData = this.exportData.bind(this);
this.resetDefaultValues = this.resetDefaultValues.bind(this);
this.beforeCellEdit = this.beforeCellEdit.bind(this);
}
private getTopRef() {
return this.table.componentRef.getTopRef();
}
private getActionArgs() {
return this.tools?.cm.actionArgs.value;
}
query() {
this.tools?.bm.query.click(this.getActionArgs());
}
moreQuery() {
this.tools?.bm.moreQuery.click(this.getActionArgs());
}
reset() {
this.tools?.bm.reset.click(this.getActionArgs());
}
refresh() {
this.tools?.bm.refresh.click(this.getActionArgs());
}
add() {
this.tools?.bm.add.click(this.getActionArgs());
}
addTop() {
this.tools?.bm.addTop.click(this.getActionArgs());
}
addChild(target: any) {
if (Tools.isEmpty(target)) {
throw new Error('[w-grid] The `addChild` method parameter cannot be null.');
}
this.tools?.apiFM.operator.setSelected(target);
this.tools?.bm.addChild.click(this.getActionArgs());
}
edit(target: any) {
if (Tools.isEmpty(target)) {
throw new Error('[w-grid] The `edit` method parameter cannot be null.');
}
this.tools?.apiFM.operator.setSelected(target);
this.tools?.bm.edit.click(this.getActionArgs());
}
cellEdit(targetRow: any, targetColName: string) {
this.beforeCellEdit(targetRow, targetColName);
this.tools?.bm.cellEdit.click(this.getActionArgs());
}
inlineCellEdit(targetRow: any, targetColName: string) {
this.beforeCellEdit(targetRow, targetColName);
this.tools?.bm.inlineCellEdit.click(this.getActionArgs());
}
inlineRowEdit(target: any) {
this.tools?.apiFM.operator.setSelected(target);
this.tools?.bm.inlineRowEdit.click(this.getActionArgs());
}
inlineRowsEdit() {
this.tools?.bm.inlineRowsEdit.click(this.getActionArgs());
}
clone(target: any) {
this.tools?.apiFM.operator.setSelected(target);
this.tools?.bm.clone.click(this.getActionArgs());
}
remove(target: any) {
this.tools?.apiFM.operator.setSelected(target);
this.getTopRef()?.remove(this.getActionArgs());
}
view(target: any) {
this.tools?.apiFM.operator.setSelected(target);
this.tools?.bm.view.click(this.getActionArgs());
}
exportData() {
this.tools?.bm.expand.click(this.getActionArgs());
}
resetDefaultValues() {
this.tools?.bm.resetDefaultValues.click(this.getActionArgs());
}
private beforeCellEdit(targetRow: any, targetColName: string) {
if (!this.table) {
return;
}
if (Tools.isEmpty(targetRow)) {
throw new Error('[w-grid] The `targetRow` parameter of the `cellEdit` method cannot be null.');
}
if (Tools.isEmpty(targetColName)) {
throw new Error('[w-grid] The `targetColName` parameter of the `cellEdit` method cannot be null.');
}
const result = this.tools?.dataFM.getRowsByTarget(this.table.rows, targetRow);
if (!result || result.length === 0) {
throw new Error('[w-grid] The `targetRow` does not exist in the table data.');
}
const row = result[0];
if (!row[targetColName]) {
throw new Error('[w-grid] The `targetColName` does not exist in the table data.');
}
this.table.store.cellSelected = {
row: toRaw(row),
rowKey: row[Constant.FIELD_NAMES.ROW_KEY],
primaryKey: row[this.props.primaryKey],
colName: targetColName,
value: row[targetColName],
};
}
}

40
io.sc.platform.core.frontend/src/platform/components/grid/ts/expose-api/src/ComponentRef.ts

@ -0,0 +1,40 @@
import { Base } from '../../Base';
import { PropsType, TableType } from '../../index';
/**
* w-grid ref对象API
*/
export class ComponentRef extends Base {
constructor(props: PropsType, table: TableType) {
super(props, table);
this.getQueryForm = this.getQueryForm.bind(this);
this.getEditorDialog = this.getEditorDialog.bind(this);
this.getCellEditorDialog = this.getCellEditorDialog.bind(this);
this.getEditorForm = this.getEditorForm.bind(this);
this.getCellEditorForm = this.getCellEditorForm.bind(this);
this.getViewerDrawer = this.getViewerDrawer.bind(this);
this.getViewerPanel = this.getViewerPanel.bind(this);
}
getQueryForm() {
return this.table.componentRef.getTopRef().getQueryForm();
}
getEditorDialog() {
return this.table.componentRef.getEditorRef()?.getDialog();
}
getCellEditorDialog() {
return this.table.componentRef.getCellEditorRef()?.getDialog();
}
getEditorForm() {
return this.table.componentRef.getEditorRef()?.getForm();
}
getCellEditorForm() {
return this.table.componentRef.getCellEditorRef()?.getForm();
}
getViewerDrawer() {
return this.table.componentRef.getViewRef()?.getViewerDrawer();
}
getViewerPanel() {
return this.table.componentRef.getViewRef()?.getInfoPanel();
}
}

182
io.sc.platform.core.frontend/src/platform/components/grid/ts/expose-api/src/GetData.ts

@ -0,0 +1,182 @@
import { toRaw } from 'vue';
import { Base } from '../../Base';
import { PropsType, TableType } from '../../index';
/**
* w-grid API
*/
export class GetData extends Base {
constructor(props: PropsType, table: TableType) {
super(props, table);
this.getRows = this.getRows.bind(this);
this.getSelectedRow = this.getSelectedRow.bind(this);
this.getSelectedRows = this.getSelectedRows.bind(this);
this.getTickedRow = this.getTickedRow.bind(this);
this.getTickedRows = this.getTickedRows.bind(this);
this.getSelectedCell = this.getSelectedCell.bind(this);
this.getCascadeChildren = this.getCascadeChildren.bind(this);
this.getCascadeParents = this.getCascadeParents.bind(this);
this.getSelectRowsByFieldName = this.getSelectRowsByFieldName.bind(this);
this.getCascadeChildrenData = this.getCascadeChildrenData.bind(this);
this.getCascadeParentsData = this.getCascadeParentsData.bind(this);
}
/**
*
*/
getRows() {
return toRaw(this.table?.rows);
}
/**
*
* @return
*/
getSelectedRow() {
const selectedRows = [];
if (!this.table) {
return selectedRows;
}
this.getSelectRowsByFieldName(this.table.rows, selectedRows, this.props.selectedField);
if (selectedRows && selectedRows.length > 0) {
return toRaw(selectedRows)[0];
} else {
return undefined;
}
}
/**
*
* @return
*/
getSelectedRows() {
const selectedRows = [];
if (!this.table) {
return selectedRows;
}
this.getSelectRowsByFieldName(this.table.rows, selectedRows, this.props.selectedField);
return toRaw(selectedRows);
}
/**
* checkbox勾选的记录
* @param containsNullStatus
* @return
*/
getTickedRow = (containsNullStatus: boolean = false) => {
const tickedRows = [];
if (!this.table) {
return tickedRows;
}
this.getSelectRowsByFieldName(this.table.rows, tickedRows, this.props.tickedField, containsNullStatus);
if (tickedRows && tickedRows.length > 0) {
return toRaw(tickedRows)[0];
} else {
return undefined;
}
};
/**
* checkbox勾选的所有记录
* @param containsNullStatus
* return
*/
getTickedRows(containsNullStatus: boolean = false) {
const tickedRows = [];
if (!this.table) {
return tickedRows;
}
this.getSelectRowsByFieldName(this.table.rows, tickedRows, this.props.tickedField, containsNullStatus);
return toRaw(tickedRows);
}
/**
*
*/
getSelectedCell() {
return {
row: this.table.store.cellSelected?.row,
colName: this.table.store.cellSelected?.colName,
value: this.table.store.cellSelected?.value,
};
}
/**
*
* @param fieldName
* @return
*
*/
getCascadeChildren(fieldName: string) {
const row = this.getSelectedRow();
const rows = row ? [row] : [];
return this.getCascadeChildrenData(rows, fieldName);
}
/**
*
* @param fieldName
* @return
*
*/
getCascadeParents(fieldName: string) {
const row = this.getSelectedRow();
if (row) {
const parent = this.getCascadeParentsData(row, fieldName);
const result = <any>[fieldName ? row[fieldName] : row];
if (parent && parent.length > 0) {
result.push(...parent);
}
return result;
}
return undefined;
}
private getSelectRowsByFieldName(arr: Array<any>, selectedRows: Array<any>, fieldName: string, containsNullStatus: boolean = false) {
arr.forEach((item) => {
if (containsNullStatus && (item[fieldName] || item[fieldName] === null)) {
selectedRows.push(item);
} else if (item[fieldName]) {
selectedRows.push(item);
}
if (this.props.tree && item.children && item.children.length > 0) {
this.getSelectRowsByFieldName(item.children, selectedRows, fieldName);
}
});
}
private getCascadeChildrenData(rows: Array<any>, fieldName: string) {
const data = <any>[];
if (rows && rows.length > 0) {
rows.forEach((item: any, index) => {
data.push(fieldName ? item[fieldName] : item);
if (this.props.tree && item.children && item.children.length > 0) {
const childrenData = this.getCascadeChildrenData(item.children, fieldName);
data.push(...childrenData);
}
});
}
return data;
}
/**
*
* @param row
* @param fieldName
* @returns
*/
public getCascadeParentsData(row: any, fieldName: string | null | undefined) {
const data = <any>[];
if (row && row[this.props.foreignKey]) {
const parent = this.tools?.dataFM.getRow(this.table.rows, row[this.props.foreignKey], true);
if (parent) {
data.push(fieldName ? parent[fieldName] : parent);
if (parent[this.props.foreignKey]) {
const parentData = this.getCascadeParentsData(parent, fieldName);
data.push(...parentData);
}
}
}
return data;
}
}

209
io.sc.platform.core.frontend/src/platform/components/grid/ts/expose-api/src/LocalMode.ts

@ -0,0 +1,209 @@
import { Tools, TreeBuilder } from '@/platform';
import { Base } from '../../Base';
import { Constant, PropsType, TableType } from '../../index';
/**
* w-grid API
*/
export class LocalMode extends Base {
constructor(props: PropsType, table: TableType) {
super(props, table);
this.setLocalData = this.setLocalData.bind(this);
this.addLocalData = this.addLocalData.bind(this);
this.updateLocalData = this.updateLocalData.bind(this);
this.removeLocalData = this.removeLocalData.bind(this);
this.treeAddLocalData = this.treeAddLocalData.bind(this);
this.updateLocalDataError = this.updateLocalDataError.bind(this);
this.replaceRowHandler = this.replaceRowHandler.bind(this);
this.removeTreeRows = this.removeTreeRows.bind(this);
}
/**
*
* @param rows
* @return
*/
setLocalData(rows: Array<any>) {
if (rows && Array.isArray(rows)) {
if (this.props.tree && this.props.treeRelationship === 'parent') {
const treeRows = TreeBuilder.build(rows, this.props.foreignKey, this.props.primaryKey);
this.table.rows = treeRows;
} else {
this.table.rows = rows;
}
this.tools?.dataFM.setExtraProperty(this.table.rows, undefined, true);
this.table.store.pagination.rowsNumber = this.table.rows.length;
this.tools?.opFM.resetStyleVariableValue();
}
}
/**
*
* @param data
* @param index
* @return
*/
addLocalData(data: any, index?: number) {
let success = false;
if (this.props.tree) {
success = this.treeAddLocalData(data);
} else {
if (data && Array.isArray(data) && data.length > 0) {
data.forEach((row) => {
if (!index || index >= this.table.rows.length) {
this.table.rows.push(row);
} else {
this.table.rows.splice(index, 0, row);
}
});
success = true;
} else if (data) {
if (!index || index >= this.table.rows.length) {
this.table.rows.push(data);
} else {
this.table.rows.splice(index, 0, data);
}
success = true;
}
}
if (success) {
this.tools?.dataFM.setExtraProperty(this.table.rows);
this.table.store.pagination.rowsNumber = this.table.rows.length;
this.tools?.opFM.resetStyleVariableValue();
}
}
/**
*
* @param data
* @param fieldName
* @return
*/
updateLocalData(data: any, fieldName: string = '') {
if (data && Array.isArray(data)) {
data.forEach((item) => {
this.updateLocalDataError(fieldName, item);
this.replaceRowHandler(this.table.rows, item, fieldName);
});
} else if (data) {
this.updateLocalDataError(fieldName, data);
this.replaceRowHandler(this.table.rows, data, fieldName);
}
}
/**
* TODO: 待优化
* @param target
* @return
*/
removeLocalData(target) {
const result = this.tools?.dataFM.getRowsByTarget(this.table.rows, target);
if (result && result.length > 0) {
if (this.props.tree) {
result.forEach((item) => {
this.removeTreeRows(this.table.rows, item);
});
this.tools?.dataFM.setExtraProperty(this.table.rows);
} else {
result.forEach((item) => {
this.table.rows.splice(
this.table.rows.findIndex((v) => {
if (!Tools.isEmpty(v[this.props.primaryKey]) && Tools.isEmpty(item[this.props.primaryKey])) {
return v[this.props.primaryKey] === item[this.props.primaryKey];
} else {
return v[Constant.FIELD_NAMES.ROW_KEY] === item[Constant.FIELD_NAMES.ROW_KEY];
}
}),
1,
);
});
this.table.store.pagination.rowsNumber = this.table.rows.length;
}
this.tools?.opFM.resetHeaderCheckbox();
this.tools?.opFM.resetStyleVariableValue();
}
}
/**
*
* @param data
*/
private treeAddLocalData(data: any) {
let success = false;
if (data && Array.isArray(data) && data.length > 0) {
data.forEach((row) => {
const foreignKey = row[this.props.foreignKey];
if (foreignKey) {
const parent = this.tools?.dataFM.getRow(this.table.rows, foreignKey, false, this.props.primaryKey);
if (parent) {
if (parent.children) {
parent.children.push(row);
} else {
parent.children = [row];
}
} else {
this.table.rows.push(row);
}
} else {
this.table.rows.push(row);
}
});
success = true;
} else if (data) {
const foreignKey = data[this.props.foreignKey];
if (foreignKey) {
const parent = this.tools?.dataFM.getRow(this.table.rows, foreignKey, false, this.props.primaryKey);
if (parent) {
if (parent.children) {
parent.children.push(data);
} else {
parent.children = [data];
}
} else {
this.table.rows.push(data);
}
} else {
this.table.rows.push(data);
}
success = true;
}
return success;
}
private updateLocalDataError(fieldName: string, data: any) {
if (Tools.isEmpty(fieldName) && Tools.isEmpty(data[this.props.primaryKey]) && Tools.isEmpty(data[Constant.FIELD_NAMES.ROW_KEY])) {
throw new Error('[w-grid]Data does not contain `primaryKey` or `rowKey`.');
}
}
private replaceRowHandler(arr: Array<any>, row: any, fieldName: string) {
for (let i = 0; i < arr.length; i++) {
if (!Tools.isEmpty(fieldName) && row[fieldName] === arr[i][fieldName]) {
// 根据设定的字段进行匹配
arr[i] = { ...arr[i], ...row };
break;
} else if (Tools.isEmpty(fieldName) && !Tools.isEmpty(row[this.props.primaryKey]) && row[this.props.primaryKey] === arr[i][this.props.primaryKey]) {
// 未设定根据哪个字段匹配时首先尝试用主键进行匹配。
arr[i] = { ...arr[i], ...row };
break;
} else if (Tools.isEmpty(fieldName) && row[Constant.FIELD_NAMES.ROW_KEY] === arr[i][Constant.FIELD_NAMES.ROW_KEY]) {
// 主键匹配失败则尝试用前端唯一键进行匹配
arr[i] = { ...arr[i], ...row };
break;
} else if (this.props.tree && arr[i].children && arr[i].children.length > 0) {
this.replaceRowHandler(arr[i].children, row, fieldName);
}
}
}
private removeTreeRows(arr: Array<any>, row: any) {
arr.forEach((item, index) => {
if (row[this.props.primaryKey] === item[this.props.primaryKey] || row[Constant.FIELD_NAMES.ROW_KEY] === item[Constant.FIELD_NAMES.ROW_KEY]) {
arr.splice(index, 1);
} else if (item.children && item.children.length > 0) {
this.removeTreeRows(item.children, row);
}
});
}
}

183
io.sc.platform.core.frontend/src/platform/components/grid/ts/expose-api/src/Operator.ts

@ -0,0 +1,183 @@
import { nextTick } from 'vue';
import { Base } from '../../Base';
import { Constant, PropsType, TableType } from '../../index';
/**
* w-grid API
*/
export class Operator extends Base {
constructor(props: PropsType, table: TableType) {
super(props, table);
this.refreshGrid = this.refreshGrid.bind(this);
this.viewData = this.viewData.bind(this);
this.setSelected = this.setSelected.bind(this);
this.setTicked = this.setTicked.bind(this);
this.clearSelected = this.clearSelected.bind(this);
this.clearTicked = this.clearTicked.bind(this);
this.expand = this.expand.bind(this);
this.collapse = this.collapse.bind(this);
this.processTree = this.processTree.bind(this);
this.processGroup = this.processGroup.bind(this);
this.setSelectedStatus = this.setSelectedStatus.bind(this);
this.setTickedStatus = this.setTickedStatus.bind(this);
}
/**
*
*/
refreshGrid() {
nextTick(() => {
this.tools?.reqApiFM.fetchData({ pagination: this.table.store.pagination });
this.table.store.headerTicked = false;
});
}
/**
*
*/
viewData() {
this.table.componentRef.getViewRef()?.view();
}
/**
*
* @param target
* @return
*/
setSelected(target: any) {
if (target) {
const result = this.tools?.dataFM.getRowsByTarget(this.table.rows, target);
if (result && result.length > 0) {
this.setSelectedStatus(result, true);
}
}
}
/**
* checkbox勾选
* @param target
* @return
*/
setTicked(target: any) {
if (target) {
const result = this.tools?.dataFM.getRowsByTarget(this.table.rows, target);
if (result && result.length > 0) {
this.setTickedStatus(result, true);
}
}
}
/**
*
* @param target
* @return
*/
clearSelected(target: any = undefined) {
if (target || (Array.isArray(target) && target.length > 0)) {
const result = this.tools?.dataFM.getRowsByTarget(this.table.rows, target);
if (result && result.length > 0) {
this.setSelectedStatus(result, false);
}
} else if (this.table) {
this.setSelectedStatus(this.table?.rows, false);
}
}
/**
* checkbox勾选记录
* @param target
* @return
*/
clearTicked(target: any = undefined) {
if (target || (Array.isArray(target) && target.length > 0)) {
const result = this.tools?.dataFM.getRowsByTarget(this.table.rows, target);
if (result && result.length > 0) {
this.setTickedStatus(result, false);
}
} else if (this.table) {
this.setTickedStatus(this.table.rows, false);
}
}
/**
*
* @param target
*
* @param cascadeChildren
*/
expand(target: any = undefined, cascadeChildren: boolean = false) {
const expand = true;
if (this.props.tree) {
// 树表格模式
this.processTree(target, expand, cascadeChildren);
} else {
// 独立分组模式
this.processGroup(target, expand);
}
}
/**
*
* @param target
*
* @param cascadeParents
*/
collapse(target: any = undefined, cascadeParents: boolean = false) {
const expand = false;
if (this.props.tree) {
// 树表格模式
this.processTree(target, expand, cascadeParents);
} else {
// 独立分组模式
this.processGroup(target, expand);
}
}
private processTree(target: any, expand: boolean, cascade: boolean) {
const rows = target ? this.tools?.dataFM.getRowsByTarget(this.table.rows, target) : this.table.rows;
if (rows) {
rows.forEach((item: any) => {
item[Constant.FIELD_NAMES.EXPAND] = expand;
if (cascade && item.children && item.children.length > 0) {
this.tools?.opFM.resetTreeGridExpand(item.children, expand);
} else if (!expand) {
const parents = this.tools?.apiFM.getData.getCascadeParentsData(item, null);
if (parents && parents.length > 0) {
this.tools?.opFM.resetTreeGridExpand(parents, expand);
}
}
});
}
}
private processGroup(target: any, expand: boolean) {
if (target) {
const findResult = this.table.configStore.aloneGroupRecords.find((item) => item.groupName === target);
if (!findResult) {
console.error('[w-grid] groupRecords not find `' + target + '`.');
return;
}
findResult['expand'] = expand;
} else {
this.table.configStore.aloneGroupRecords.forEach((item) => {
item['expand'] = expand;
});
}
}
private setSelectedStatus(arr: Array<any>, status: boolean) {
arr.forEach((item) => {
item[this.props.selectedField] = status;
if (this.props.tree && item.children && item.children.length > 0) {
this.setSelectedStatus(item.children, status);
}
});
}
private setTickedStatus(arr: Array<any>, status: boolean, cascadeChildren: boolean = true) {
arr.forEach((item) => {
item[this.props.tickedField] = status;
if (this.props.tree && cascadeChildren && item.children && item.children.length > 0) {
this.setTickedStatus(item.children, status, cascadeChildren);
}
});
}
}

118
io.sc.platform.core.frontend/src/platform/components/grid/ts/expose-api/src/ResetProperty.ts

@ -0,0 +1,118 @@
import { Base } from '../../Base';
import { PropsType, TableType } from '../../index';
/**
* w-grid API
*/
export class ResetProperty extends Base {
constructor(props: PropsType, table: TableType) {
super(props, table);
this.setQueryCriteria = this.setQueryCriteria.bind(this);
this.setQueryCriteriaFieldValue = this.setQueryCriteriaFieldValue.bind(this);
this.setDataUrl = this.setDataUrl.bind(this);
this.setFetchDataUrl = this.setFetchDataUrl.bind(this);
this.setAddDataUrl = this.setAddDataUrl.bind(this);
this.setEditDataUrl = this.setEditDataUrl.bind(this);
this.setRemoveDataUrl = this.setRemoveDataUrl.bind(this);
this.setQueryCriteriaFieldValueHandler = this.setQueryCriteriaFieldValueHandler.bind(this);
}
/**
* criteria
* @param criteria
* @returns
*/
setQueryCriteria(criteria: any) {
if (!this.table) {
return;
}
this.table.queryCriteria = criteria;
}
/**
* criteria中的字段值
* @param fieldName
* @param fieldValue
* @return
*/
setQueryCriteriaFieldValue(fieldName: string, fieldValue: any) {
if (Object.keys(this.table?.queryCriteria).length === 0) {
console.error('[w-grid]The property `queryCriteria` is configured.');
return;
}
this.setQueryCriteriaFieldValueHandler(this.table?.queryCriteria, fieldName, fieldValue);
}
/**
* URL
* @param url
*/
setDataUrl(url: string) {
if (!this.table) {
return;
}
this.table.url.dataUrl = url;
}
/**
* URL
* @param url
* @returns
*/
setFetchDataUrl(url: string) {
if (!this.table) {
return;
}
this.table.url.fetchDataUrl = url;
}
/**
* URL
* @param url
* @returns
*/
setAddDataUrl(url: string) {
if (!this.table) {
return;
}
this.table.url.addDataUrl = url;
}
/**
* URL
* @param url
* @returns
*/
setEditDataUrl(url: string) {
if (!this.table) {
return;
}
this.table.url.editDataUrl = url;
}
/**
* url
* @param url
* @returns
*/
setRemoveDataUrl(url: string) {
if (!this.table) {
return;
}
this.table.url.removeDataUrl = url;
}
private setQueryCriteriaFieldValueHandler(criteria: any, fieldName: string, fieldValue: any) {
if (criteria.criteria && criteria.criteria.length > 0) {
criteria.criteria.forEach((item) => {
if (!item.criteria && item.fieldName && item.fieldName === fieldName) {
item.value = fieldValue;
} else {
this.setQueryCriteriaFieldValueHandler(item.criteria, fieldName, fieldValue);
}
});
} else if (criteria.fieldName && criteria.fieldName === fieldName) {
criteria.value = fieldValue;
}
}
}

158
io.sc.platform.core.frontend/src/platform/components/grid/ts/function/Criteria.ts

@ -0,0 +1,158 @@
import { Tools } from '@/platform';
import { nextTick } from 'vue';
import { Constant, PropsType, GridTools } from '../index';
import { Base } from '../Base';
import { criteriaOperator } from '../../../query-builder/criteria';
import { TableType } from '../types/TableType';
// 定义数据结构类型
interface CriteriaType {
fieldName?: string;
operator: string;
value?: any;
start?: any;
end?: any;
criteria?: CriteriaType[];
}
/**
* w-grid Criteria相关函数
*/
export class Criteria extends Base {
/**
*
*/
queryFormFields: any;
constructor(props: PropsType, table: TableType) {
super(props, table);
this.queryFormFields = GridTools.arrayToObject(props.queryFormFields, 'name');
this.buildCriteria = this.buildCriteria.bind(this);
this.buildURLSearchParams = this.buildURLSearchParams.bind(this);
}
/**
* criteria
* @param value
* @param fieldName
* @returns
*/
private buildCriteria(value: any, fieldName: string) {
const queryOperator = this.queryFormFields[fieldName]['queryOperator'];
if (!Tools.isEmpty(queryOperator)) {
return {
fieldName: fieldName,
operator: queryOperator,
value: value,
};
} else if (typeof value === 'boolean' || typeof value === 'number' || (typeof value === 'string' && this.queryFormFields[fieldName]['type'] === 'w-date')) {
return {
fieldName: fieldName,
operator: Constant.CRITERIA_OPERATOR.equals,
value: value,
};
} else if (Array.isArray(value)) {
return {
fieldName: fieldName,
operator: Constant.CRITERIA_OPERATOR.inSet,
value: value,
};
} else if (typeof value === 'object' && this.queryFormFields[fieldName]['type'] === 'w-date-range') {
return {
fieldName: fieldName,
operator: Constant.CRITERIA_OPERATOR.betweenInclusive,
start: value['from'],
end: value['to'],
};
} else {
return {
fieldName: fieldName,
operator: Constant.CRITERIA_OPERATOR.contains,
value: value,
};
}
}
/**
* criteria URLSearchParams
* @param reqParams
* @returns URLSearchParams
*/
buildURLSearchParams(reqParams) {
const urlSearchParams = new URLSearchParams(reqParams);
// 处理默认查询条件
if (Object.keys(this.table.queryCriteria).length > 0) {
urlSearchParams.append('criteria', JSON.stringify(this.table.queryCriteria));
}
if (this.table.advancedQueryStatus) {
const conditions = this.advancedQueryConditions();
if (conditions && conditions.criteria.length > 0) {
urlSearchParams.append('criteria', JSON.stringify(conditions));
}
} else {
const queryForm = this.table.componentRef.getTopRef()?.getQueryForm();
if (queryForm) {
nextTick(() => {
const queryFormData = queryForm.getData();
Object.keys(queryFormData).forEach((item) => {
if (
(!Tools.isEmpty(queryFormData[item]) && !Array.isArray(queryFormData[item])) ||
(!Tools.isEmpty(queryFormData[item]) && Array.isArray(queryFormData[item]) && queryFormData[item].length > 0)
) {
// 根据数据进行operator处理
const criteria = this.buildCriteria(queryFormData[item], item);
urlSearchParams.append('criteria', JSON.stringify(criteria));
}
});
});
}
}
return urlSearchParams;
}
/**
*
* @returns
*/
private advancedQueryConditions() {
let conditions: any = undefined;
if (this.table.advancedQueryModelValue && this.table.advancedQueryModelValue['criteria'][0]['fieldName']) {
conditions = {
...this.table.advancedQueryModelValue,
criteria: this.cleanCriteria(this.table.advancedQueryModelValue['criteria']),
};
}
return conditions;
}
/**
*
* criteria
* @param criteria
* @returns
*/
private cleanCriteria(criteria: CriteriaType[]): CriteriaType[] {
return criteria.filter((c) => {
if (Tools.isEmpty(c.fieldName)) {
return false;
} else if (c.operator === criteriaOperator.between.name || c.operator === criteriaOperator.notBetween.name) {
if (Tools.isEmpty(c.start) && Tools.isEmpty(c.end)) {
return false;
}
} else if (
c.operator !== criteriaOperator.isBlank.name &&
c.operator !== criteriaOperator.notBlank.name &&
c.operator !== criteriaOperator.isNull.name &&
c.operator !== criteriaOperator.notNull.name &&
Tools.isEmpty(c.value)
) {
return false;
}
// 如果存在嵌套的criteria,则递归调用
if (c.criteria) {
c.criteria = this.cleanCriteria(c.criteria);
}
return true;
});
}
}

411
io.sc.platform.core.frontend/src/platform/components/grid/ts/function/DragAndDrop.ts

@ -0,0 +1,411 @@
import { Tools } from '@/platform';
import { toRaw, Ref, ref } from 'vue';
import { Constant, PropsType, TableType } from '../index';
import { Base } from '../Base';
/**
* w-grid
*/
export class DragAndDrop extends Base {
/**
* 线
*/
static DRAG_LINE_STYLE = {
width: '2px',
style: 'dashed',
color: 'orange',
};
static DRAG_ICON = {
// 拖拽时父节点图标
name: 'bi-folder-symlink-fill',
color: 'amber',
};
private static OPERATOR_ADD = 'add';
private static OPERATOR_REMOVE = 'remove';
private static POSITION_TOP = 'borderTop';
private static POSITION_BOTTOM = 'borderBottom';
/**
*
*/
timer: any = undefined;
/**
*
*/
iconAndNameDwellTime: Ref = ref(0);
/**
*
*/
dwellRow: any = undefined;
// 待更新排序数据
private updateOrderData = <any>[];
constructor(props: PropsType, table: TableType) {
super(props, table);
this.onDragStart = this.onDragStart.bind(this);
this.onDragEnd = this.onDragEnd.bind(this);
this.iconAndNameDragOver = this.iconAndNameDragOver.bind(this);
this.onDragOver = this.onDragOver.bind(this);
this.onDragLeave = this.onDragLeave.bind(this);
this.onDrop = this.onDrop.bind(this);
this.setDragIcon = this.setDragIcon.bind(this);
this.destroyTimer = this.destroyTimer.bind(this);
this.dropTargetIsIconAndName = this.dropTargetIsIconAndName.bind(this);
this.resetOrder = this.resetOrder.bind(this);
this.resetOrderDataHandler = this.resetOrderDataHandler.bind(this);
this.removeRecord = this.removeRecord.bind(this);
this.nodeSplice = this.nodeSplice.bind(this);
this.dragLessThanTarget = this.dragLessThanTarget.bind(this);
this.setOrder = this.setOrder.bind(this);
this.childrenResetOrder = this.childrenResetOrder.bind(this);
this.canDrag = this.canDrag.bind(this);
this.iconAndNameCanDrag = this.iconAndNameCanDrag.bind(this);
this.dragRecordsContains = this.dragRecordsContains.bind(this);
this.setDndStyle = this.setDndStyle.bind(this);
}
// 拖拽开始
public onDragStart(e: any, scope: any) {
const img = new Image();
img.src = Constant.DND_BG_IMAGE;
e.dataTransfer.setDragImage(img, 0, 0);
const selecteds = this.instance.getSelectedRows();
if (
selecteds.length > 0 &&
selecteds.findIndex((item) => {
return item[Constant.FIELD_NAMES.ROW_KEY] === scope.row[Constant.FIELD_NAMES.ROW_KEY];
}) > -1
) {
this.table.store.dragRecords = selecteds;
} else {
this.instance.clearSelected();
// 触发选择
scope.row[this.props.selectedField] = true;
this.table.store.dragRecords = [scope.row];
}
e.dataTransfer.effectAllowed = 'move';
}
// 销毁计时器
private destroyTimer() {
if (this.timer) {
clearInterval(this.timer);
this.timer = undefined;
}
this.iconAndNameDwellTime.value = 0;
this.dwellRow = undefined;
}
// 拖拽结束
onDragEnd(e: any, scope: any) {
this.destroyTimer();
}
/**
*
* @param e
* @param scope
*/
iconAndNameDragOver(e: any, row: any) {
if (!this.iconAndNameCanDrag(this.table?.store.dragRecords, row[this.props.primaryKey])) {
this.setDndStyle(e, DragAndDrop.OPERATOR_REMOVE, DragAndDrop.POSITION_TOP);
this.setDndStyle(e, DragAndDrop.OPERATOR_REMOVE, DragAndDrop.POSITION_BOTTOM);
this.setDragIcon(this.table?.rows);
e.dataTransfer.dropEffect = 'none';
} else {
if (!this.timer) {
this.timer = setInterval(() => {
this.iconAndNameDwellTime.value++;
}, 500);
this.dwellRow = row;
}
row['_dragIcon_'] = DragAndDrop.DRAG_ICON.name;
}
}
// 拖拽过程触发
onDragOver(e: any, scope: any, trMiddleHeight: number) {
e.preventDefault();
if (!this.canDrag(this.table?.store.dragRecords, scope.row[this.props.foreignKey])) {
this.setDndStyle(e, DragAndDrop.OPERATOR_REMOVE, DragAndDrop.POSITION_TOP);
this.setDndStyle(e, DragAndDrop.OPERATOR_REMOVE, DragAndDrop.POSITION_BOTTOM);
this.setDragIcon(this.table?.rows);
e.dataTransfer.dropEffect = 'none';
} else {
if (e.offsetY <= trMiddleHeight && !this.dragRecordsContains(scope.row)) {
this.setDndStyle(e, DragAndDrop.OPERATOR_REMOVE, DragAndDrop.POSITION_BOTTOM);
this.setDndStyle(e, DragAndDrop.OPERATOR_ADD, DragAndDrop.POSITION_TOP);
} else if (e.offsetY > trMiddleHeight && !this.dragRecordsContains(scope.row)) {
this.setDndStyle(e, DragAndDrop.OPERATOR_REMOVE, DragAndDrop.POSITION_TOP);
this.setDndStyle(e, DragAndDrop.OPERATOR_ADD, DragAndDrop.POSITION_BOTTOM);
}
if (!this.dropTargetIsIconAndName(e)) {
this.destroyTimer();
if (!Tools.isUndefinedOrNull(scope.row[this.props.foreignKey])) {
this.setDragIcon(this.table?.rows, scope.row[this.props.foreignKey]);
} else {
this.setDragIcon(this.table?.rows);
}
}
e.dataTransfer.dropEffect = 'move';
}
e.stopPropagation();
}
/**
* div
* @param e
* @returns
*/
private dropTargetIsIconAndName(e) {
return typeof e.target.className === 'string' && e.target.className.split(' ').includes('treeGridIconAndName');
}
// 拖拽至不可放置区域触发
onDragLeave(e) {
this.setDndStyle(e, DragAndDrop.OPERATOR_REMOVE, DragAndDrop.POSITION_TOP);
this.setDndStyle(e, DragAndDrop.OPERATOR_REMOVE, DragAndDrop.POSITION_BOTTOM);
this.destroyTimer();
}
// 拖拽放置时触发
onDrop(e: any, scope: any, trMiddleHeight: number) {
e.preventDefault();
this.setDndStyle(e, DragAndDrop.OPERATOR_REMOVE, DragAndDrop.POSITION_TOP);
this.setDndStyle(e, DragAndDrop.OPERATOR_REMOVE, DragAndDrop.POSITION_BOTTOM);
this.setDragIcon(this.table?.rows);
if (!this.table) {
return false;
}
if (
this.table.store.dragRecords.findIndex((item) => {
return item[Constant.FIELD_NAMES.ROW_KEY] === scope.row[Constant.FIELD_NAMES.ROW_KEY];
}) > -1
) {
return;
}
// 每次放置时清空待更新集合
this.updateOrderData.splice(0, this.updateOrderData.length);
if (this.dropTargetIsIconAndName(e)) {
this.table.store.dragRecords.forEach((item) => {
// 从表格数据中将拖拽数据删除
this.removeRecord(this.table?.rows, item);
// 重新设置父
item[this.props.foreignKey] = scope.row[this.props.primaryKey];
// 将拖动的数据全部加入到父的children中
if (scope.row.children) {
scope.row.children.push(item);
} else {
scope.row.children = [item];
}
this.updateOrderData.push(item);
});
} else {
// 将拖动的数据及其子节点放到目标数据位置,并将目标数据父节点下的子节点重新排序。
this.resetOrder(e, this.table.store.dragRecords, this.table.rows, scope.row, trMiddleHeight);
}
// 请求后端更新排序
if (this.props.dndMode === Constant.DND_MODE.SERVER && this.updateOrderData.length > 0) {
this.tools?.reqApiFM.updates(this.updateOrderData);
}
this.tools?.em.afterDragAndDrop(this.updateOrderData);
}
// 重新设置树的排序
private resetOrder(e, dragRecords, arr, targetData, trMiddleHeight: number) {
for (let i = 0; i < arr.length; i++) {
if (arr[i][Constant.FIELD_NAMES.ROW_KEY] === targetData[Constant.FIELD_NAMES.ROW_KEY]) {
this.resetOrderDataHandler(e, dragRecords, targetData, i, trMiddleHeight);
break;
} else if (arr[i].children && arr[i].children.length > 0) {
this.resetOrder(e, dragRecords, arr[i].children, targetData, trMiddleHeight);
}
}
}
// 数据处理
private resetOrderDataHandler(e, dragRecords, targetData, targetIndex, trMiddleHeight: number) {
dragRecords.forEach((item) => {
// 从表格数据中将拖拽数据删除
const itemIndex = this.removeRecord(this.table?.rows, item);
if (Tools.isEmpty(targetData[this.props.foreignKey])) {
this.nodeSplice(e, itemIndex, targetIndex, item, targetData, this.table?.rows, trMiddleHeight);
item[this.props.foreignKey] = null;
this.setOrder(this.table?.rows);
} else {
this.childrenResetOrder(e, this.table?.rows, item, targetData, itemIndex, targetIndex, trMiddleHeight);
}
});
}
// 删除表格中的数据
private removeRecord(arr, removeData) {
let index = 0;
for (let i = 0; i < arr.length; i++) {
if (arr[i][Constant.FIELD_NAMES.ROW_KEY] === removeData[Constant.FIELD_NAMES.ROW_KEY]) {
arr.splice(i, 1);
index = i;
break;
} else if (arr[i].children && arr[i].children.length > 0) {
const a = this.removeRecord(arr[i].children, removeData);
if (a > 0) {
index = a;
}
}
}
return index;
}
// 处理树节点的增加
private nodeSplice(e, dragIndex, targetIndex, drag, target, arr, trMiddleHeight: number) {
if (
(Tools.isEmpty(drag[this.props.foreignKey]) && Tools.isEmpty(target[this.props.foreignKey])) ||
drag[this.props.foreignKey] === target[this.props.foreignKey]
) {
const lessThanResult = this.dragLessThanTarget(dragIndex, targetIndex);
// 父节点相同的同级处理
if (e.offsetY <= trMiddleHeight && lessThanResult) {
arr.splice(targetIndex - 1, 0, drag);
} else if (e.offsetY > trMiddleHeight && !lessThanResult) {
arr.splice(targetIndex + 1, 0, drag);
} else {
arr.splice(targetIndex, 0, drag);
}
} else {
// 拖拽节点与目标节点不同级处理
if (e.offsetY <= trMiddleHeight) {
arr.splice(targetIndex, 0, drag);
} else if (e.offsetY > trMiddleHeight) {
arr.splice(targetIndex + 1, 0, drag);
}
}
}
private dragLessThanTarget(dragIndex, targetIndex) {
if (dragIndex < targetIndex) {
return true;
}
return false;
}
// 设置排序号
private setOrder(arr) {
arr.forEach((item, index) => {
item[this.props.dndOrderBy] = index + 1;
// 添加至待更新集合中
this.updateOrderData.push({ ...toRaw(item), children: null });
});
}
// 增加并重新设置父属性
private childrenResetOrder(e, arr, addData, targetData, dragIndex, targetIndex, trMiddleHeight: number) {
for (let i = 0; i < arr.length; i++) {
if (arr[i][this.props.primaryKey] === targetData[this.props.foreignKey]) {
this.nodeSplice(e, dragIndex, targetIndex, addData, targetData, arr[i].children, trMiddleHeight);
// 修改父
addData[this.props.foreignKey] = arr[i][this.props.primaryKey];
// 重新设置排序号
this.setOrder(arr[i].children);
break;
} else if (arr[i].children && arr[i].children.length > 0) {
this.childrenResetOrder(e, arr[i].children, addData, targetData, dragIndex, targetIndex, trMiddleHeight);
}
}
}
// 设置拖拽目标的父节点icon图标
setDragIcon(arr: any, key: string = '') {
for (let i = 0; i < arr.length; i++) {
if (arr[i].children && arr[i].children.length > 0) {
if (!Tools.isEmpty(key) && arr[i][this.props.primaryKey] === key) {
if (Tools.isEmpty(arr[i]['_dragIcon_']) || arr[i]['_dragIcon_'] !== DragAndDrop.DRAG_ICON.name) {
arr[i]['_dragIcon_'] = DragAndDrop.DRAG_ICON.name;
}
} else {
arr[i]['_dragIcon_'] = '';
}
this.setDragIcon(arr[i].children, key);
} else {
arr[i]['_dragIcon_'] = '';
}
}
}
/**
*
* @param dragRecords
* @param targetParentKey key
*/
private canDrag(dragRecords, targetParentKey) {
if (Tools.isEmpty(targetParentKey)) {
return true;
}
// 判断拖拽的行是否为当前鼠标所在的目标行的父节点(只要存在父,一直往上找)
let result = true;
let parentKey = targetParentKey;
while (!Tools.isEmpty(parentKey)) {
if (
dragRecords.findIndex((item) => {
return item[this.props.primaryKey] === parentKey;
}) > -1
) {
result = false;
parentKey = null;
} else {
parentKey = this.tools?.dataFM.getRow(this.table.rows, parentKey, true)[this.props.foreignKey];
}
}
return result;
}
/**
*
* @param dragRecords
* @param targetPrimaryKey
*/
private iconAndNameCanDrag(dragRecords: any, targetPrimaryKey: any) {
let result = true;
for (let i = 0; i < dragRecords.length; i++) {
const record = dragRecords[i];
if (record[this.props.primaryKey] === targetPrimaryKey) {
result = false;
break;
}
}
return result;
}
// 根据传入的数据行判断是否在当前拖拽的记录集合中
private dragRecordsContains(dataRow) {
if (!this.table) {
return false;
}
return (
this.table.store.dragRecords.findIndex((item) => {
return item[Constant.FIELD_NAMES.ROW_KEY] === dataRow[Constant.FIELD_NAMES.ROW_KEY];
}) > -1
);
}
private setDndStyle(e: any, operator: string, position: string) {
let targetChildrenNodes: any = [];
if (e.target.nodeName === 'TD') {
targetChildrenNodes = e.target?.parentNode?.children;
} else if (e.target.nodeName === 'DIV' && e.target.className.split(' ').includes('treeGridFirstTd')) {
targetChildrenNodes = e.target?.parentNode?.parentNode?.children;
}
if (targetChildrenNodes) {
for (let i = 0; i < targetChildrenNodes.length; i++) {
targetChildrenNodes[i].style[position + 'Width'] = operator === DragAndDrop.OPERATOR_ADD ? DragAndDrop.DRAG_LINE_STYLE.width : '';
targetChildrenNodes[i].style[position + 'Style'] = operator === DragAndDrop.OPERATOR_ADD ? DragAndDrop.DRAG_LINE_STYLE.style : '';
targetChildrenNodes[i].style[position + 'Color'] = operator === DragAndDrop.OPERATOR_ADD ? DragAndDrop.DRAG_LINE_STYLE.color : '';
}
}
}
}

112
io.sc.platform.core.frontend/src/platform/components/grid/ts/function/InlineEdit.ts

@ -0,0 +1,112 @@
import { Tools } from '@/platform';
import { Base } from '../Base';
import { Constant, PropsType, TableType } from '../index';
/**
* w-grid
*/
export class InlineEdit extends Base {
/**
* ref
*/
componentRef = {};
constructor(props: PropsType, table: TableType) {
super(props, table);
this.setComponentRef = this.setComponentRef.bind(this);
this.getComponentRef = this.getComponentRef.bind(this);
this.getComponentRefs = this.getComponentRefs.bind(this);
this.exitInlineEdit = this.exitInlineEdit.bind(this);
this.getEditorFieldByName = this.getEditorFieldByName.bind(this);
this.isShowInlineEditor = this.isShowInlineEditor.bind(this);
}
/**
* ref
* @param el html
* @param row
* @param col
*/
public setComponentRef(el: any, row: any, col: any) {
if (el && !Tools.isEmpty(col.name)) {
this.componentRef[row[Constant.FIELD_NAMES.ROW_KEY] + '_' + col.name] = el;
}
}
/**
* ref
* @param rowKey
* @param colName
*/
public getComponentRef(rowKey: string, colName: string) {
return this.componentRef[rowKey + '_' + colName];
}
/**
* ref
* @param rowKey
* @returns
*/
public getComponentRefs(rowKey: string | Array<string>) {
const refs = <any>[];
if (!Tools.isEmpty(this.componentRef)) {
const filterResult = Object.keys(this.componentRef).filter((item) => {
if (typeof rowKey === 'string') {
return item.startsWith(rowKey + '_');
} else {
return true;
}
});
if (filterResult.length > 0) {
filterResult.forEach((key) => {
refs.push(this.componentRef[key]);
});
}
}
return refs;
}
/**
* 退
*/
public exitInlineEdit() {
if (this.table) {
this.table.store.inlineEditStatus = Constant.EDIT_STATUS.NONE;
this.table.store.cellSelected = undefined;
}
}
/**
* editor
* @param name
* @returns
*/
public getEditorFieldByName(name: string) {
if (this.table && this.props?.editor?.form?.fields) {
const field = this.props.editor.form.fields.find((item) => item['name'] === name);
return field;
}
return undefined;
}
/**
*
* @param col
* @param rowKey
* @returns
*/
public isShowInlineEditor(col: object, rowKey: string) {
if (Tools.hasOwnProperty(col, 'edit') && !col['edit']) {
return false;
}
if (
this.getEditorFieldByName(col['name']) &&
((this.tools?.dataFM.isSelectedRow(rowKey) && this.table.store.inlineEditStatus === Constant.EDIT_STATUS.ROW) ||
this.table?.store.inlineEditStatus === Constant.EDIT_STATUS.ROWS ||
(this.tools?.dataFM.isSelectedCell(rowKey, col['name']) && this.table.store.inlineEditStatus === Constant.EDIT_STATUS.CELL))
) {
return true;
}
return false;
}
}

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

@ -0,0 +1,272 @@
import { Tools } from '@/platform';
import { nextTick } from 'vue';
import { Base } from '../Base';
import { Constant, PropsType, TableType } from '../index';
/**
* w-grid
*/
export class Operator extends Base {
constructor(props: PropsType, table: TableType) {
super(props, table);
this.tableFullscreen = this.tableFullscreen.bind(this);
this.resetHeaderCheckbox = this.resetHeaderCheckbox.bind(this);
this.resetTreeGridExpand = this.resetTreeGridExpand.bind(this);
this.propsValidate = this.propsValidate.bind(this);
this.resize = this.resize.bind(this);
this.resetStyleVariableValue = this.resetStyleVariableValue.bind(this);
}
/**
*
* @param value true: , false: 退
*/
tableFullscreen(value) {
this.table.configStore.isFullscreen = !this.table.configStore.isFullscreen;
if (value) {
this.table.store.location.tableInFullscreenY = this.table.store.location.yLocation;
this.table.store.location.noDataTrInFullscreenY = this.table.store.location.noDataTrYLocation;
} else {
this.table.store.location.yLocation = this.table.store.location.tableInFullscreenY;
this.table.store.location.noDataTrYLocation = this.table.store.location.noDataTrInFullscreenY;
this.table.store.location.tableInFullscreenY = 0;
this.table.store.location.noDataTrInFullscreenY = 0;
}
}
/**
*
*/
public resetHeaderCheckbox() {
this.tableError();
if (this.table) {
if (this.props.checkboxSelection) {
const ticked_ = <any>[];
const rows = this.table?.rows || [];
rows.forEach((item) => {
if (item[this.props.tickedField]) {
ticked_.push(item);
}
});
if (ticked_.length === rows.length && rows.length > 0) {
// 全部勾选设置为 true
this.table.store.headerTicked = true;
} else if (ticked_.length > 0) {
// 存在一条勾选的记录设置为 null
this.table.store.headerTicked = null;
} else {
// 一条勾选的记录都没有设置为 false
this.table.store.headerTicked = false;
}
}
}
}
/**
*
* @param arr
* @param expandStatus
*/
public resetTreeGridExpand(arr: Array<any>, expandStatus: boolean) {
if (!this.props.tree) {
return;
}
arr.forEach((item) => {
item[Constant.FIELD_NAMES.EXPAND] = expandStatus;
if (item.children && item.children.length > 0) {
this.resetTreeGridExpand(item.children, expandStatus);
}
});
}
/**
*
*/
propsValidate() {
if (
!Tools.isEmpty(this.props.groupMode) &&
((typeof this.props.groupByField === 'string' && Tools.isEmpty(this.props.groupByField)) ||
(Array.isArray(this.props.groupByField) && this.props.groupByField.length === 0))
) {
console.warn('[w-grid]Configured the `groupMode` property but did not configure the `groupByField` property.');
} else if (!Tools.isEmpty(this.props.groupMode)) {
const groupByField =
typeof this.props.groupByField === 'string' ? [this.props.groupByField] : Array.isArray(this.props.groupByField) ? this.props.groupByField : undefined;
if (groupByField && this.table?.columns.findIndex((item) => groupByField.includes(item.name)) === -1) {
throw new Error('[w-grid]`groupByField` configuration cannot be found in the table columns.');
}
}
}
/**
*
* @returns
*/
resize() {
if (!this || !this.table) {
return;
}
const tableElement = this.instance?.getHtmlElement();
if (!tableElement) {
return;
}
this.table.store.resizeFlag = !this.table.store.resizeFlag;
this.table.componentRef.getTopRef()?.handleQueryFormShowField();
this.table.componentRef.getTopRef()?.handleToolbarActions();
if (tableElement) {
this.table.store.location.yLocation = tableElement.getBoundingClientRect().y;
if (tableElement.getElementsByClassName('noDataTr').length > 0) {
this.table.store.location.noDataTrYLocation = tableElement.getElementsByClassName('noDataTr')[0].getBoundingClientRect().y;
}
this.table.store.location.topHeight = tableElement.getElementsByClassName('q-table__top')[0]?.clientHeight;
this.table.store.location.middleWidth = tableElement.getElementsByClassName('q-table__middle')[0]?.clientWidth;
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;
// 判断是否有数据,没数据修改 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;
} else {
let scrollHeight = 0;
if (this.table.store.location.middleScrollWidth - this.table.store.location.middleWidth > 0) {
scrollHeight = 15;
}
this.table.store.location.middleHeight = this.table.store.location.columnHeadHeight + scrollHeight;
}
let titleTotalHeight = tableElement.getElementsByTagName('thead')[0]?.offsetHeight;
// 无数据时列头会增加一行,多表头的top距离计算会出错,需减掉多出来的提示行。
if (this.table.rows.length === 0) {
const noDataTrHeight = tableElement.getElementsByClassName('noDataTr')[0]?.offsetHeight;
if (titleTotalHeight && titleTotalHeight > 0) {
titleTotalHeight = titleTotalHeight - noDataTrHeight;
} else {
titleTotalHeight = noDataTrHeight;
}
}
this.table.store.location.titleTotalHeight = titleTotalHeight;
}
}
/**
*
* @param time
*/
public resetStyleVariableValue(time: number = 500) {
const tableElement = this.instance.getHtmlElement();
setTimeout(() => {
if (!this.table) {
this.tableError();
return;
}
if (tableElement) {
this.table.store.location.yLocation = tableElement.getBoundingClientRect()?.y;
if (tableElement.getElementsByClassName('noDataTr').length > 0) {
this.table.store.location.noDataTrYLocation = tableElement.getElementsByClassName('noDataTr')[0]?.getBoundingClientRect().y;
}
this.table.store.location.topHeight = tableElement.getElementsByClassName('q-table__top')[0]?.clientHeight;
this.table.store.location.bottomHeight = tableElement.getElementsByClassName('q-table__bottom')[0]?.clientHeight;
this.table.store.location.middleWidth = tableElement.getElementsByClassName('q-table__middle')[0]?.clientWidth;
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;
// 判断是否有数据,没数据修改 middleHeight
if (this.table?.rows?.length > 0 || this.props.hideBottom) {
this.table.store.location.middleHeight = tableElement.getElementsByClassName('q-table__middle')[0]?.clientHeight;
} else {
let scrollHeight = 0;
if (this.table.store.location.middleScrollWidth - this.table.store.location.middleWidth > 0) {
scrollHeight = 15;
}
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) {
const theadDom = tableDom.getElementsByTagName('thead')[0];
const theadTrDom = theadDom.getElementsByTagName('tr');
tableDom.style.setProperty('--columnWidth' + '-' + 0 + '-' + 0, 0 + 'px');
for (let i = 0; i < theadDom.getElementsByTagName('tr').length; i++) {
const theadThDom = theadTrDom[i].getElementsByTagName('th');
for (let k = 0; k < theadThDom.length; k++) {
tableDom.style.setProperty('--columnWidth' + '-' + (i + 1) + '-' + (k + 1), theadThDom[k].offsetWidth + 'px');
}
}
}
const column1Width = tableDom.getElementsByTagName('td')[0]?.offsetWidth;
const column2Width = tableDom.getElementsByTagName('td')[1]?.offsetWidth;
const column3Width = tableDom.getElementsByTagName('td')[2]?.offsetWidth;
const column4Width = tableDom.getElementsByTagName('td')[3]?.offsetWidth;
const column5Width = tableDom.getElementsByTagName('td')[4]?.offsetWidth;
const column6Width = tableDom.getElementsByTagName('td')[5]?.offsetWidth;
const column7Width = tableDom.getElementsByTagName('td')[6]?.offsetWidth;
const column8Width = tableDom.getElementsByTagName('td')[7]?.offsetWidth;
const column9Width = tableDom.getElementsByTagName('td')[8]?.offsetWidth;
tableDom.style.setProperty('--column1Width', column1Width + 'px');
tableDom.style.setProperty('--column2Width', column1Width + column2Width + 'px');
tableDom.style.setProperty('--column3Width', column1Width + column2Width + column3Width + 'px');
tableDom.style.setProperty('--column4Width', column1Width + column2Width + column3Width + column4Width + 'px');
tableDom.style.setProperty('--column5Width', column1Width + column2Width + column3Width + column4Width + column5Width + 'px');
tableDom.style.setProperty('--column6Width', column1Width + column2Width + column3Width + column4Width + column5Width + column6Width + 'px');
tableDom.style.setProperty(
'--column7Width',
column1Width + column2Width + column3Width + column4Width + column5Width + column6Width + column7Width + 'px',
);
tableDom.style.setProperty(
'--column8Width',
column1Width + column2Width + column3Width + column4Width + column5Width + column6Width + column7Width + column8Width + 'px',
);
tableDom.style.setProperty(
'--column9Width',
column1Width + column2Width + column3Width + column4Width + column5Width + column6Width + column7Width + column8Width + column9Width + 'px',
);
}
let titleTotalHeight = tableDom.getElementsByTagName('thead')[0]?.offsetHeight;
// 无数据时列头会增加一行,多表头的top距离计算会出错,需减掉多出来的提示行。
if (this.table.rows.length === 0) {
const noDataTrHeight = tableElement.getElementsByClassName('noDataTr')[0]?.offsetHeight;
if (titleTotalHeight && titleTotalHeight > 0) {
titleTotalHeight = titleTotalHeight - noDataTrHeight;
} else {
titleTotalHeight = noDataTrHeight;
}
}
this.table.store.location.titleTotalHeight = titleTotalHeight;
}
}, time);
tableElement.getElementsByTagName('table')[0].style.setProperty('--tableHeadBgColor', this.table?.color.headBgColor);
tableElement.getElementsByTagName('table')[0].style.setProperty('--stickyBgColor', this.table?.color.stickyBgColor);
tableElement.getElementsByTagName('table')[0].style.setProperty('--tableBorderColor', this.table?.color.borderColor);
tableElement.getElementsByTagName('table')[0].style.setProperty('--tableColumnTitleHeight', (this.tools?.cm.denseHeader.value ? 28 : 48) + 'px');
let headerPadding = '8px';
if (this.tools?.cm.denseHeader) {
headerPadding = '4px';
}
tableElement.getElementsByTagName('table')[0].style.setProperty('--tableHeaderPadding', headerPadding);
let bodyPadding = '8px';
if (this.tools?.cm.denseBody) {
bodyPadding = '4px';
}
tableElement.getElementsByTagName('table')[0].style.setProperty('--tableBodyPadding', bodyPadding);
tableElement.getElementsByTagName('table')[0].style.setProperty('--tableBodyHeight', (this.tools?.cm.denseBody.value ? 24 : 48) + 'px');
nextTick(() => {
if (this.tools?.cm.denseBody.value && !this.props.hideBottom) {
if (tableElement.getElementsByClassName('q-table__bottom').length > 0) {
tableElement.getElementsByClassName('q-table__bottom')[0].style.setProperty('--tableBottomHeight', 33 + 'px');
tableElement.getElementsByClassName('q-table__bottom')[0].style.setProperty('--tableBottomButtonHeight', 24 + 'px');
}
} else if (!this.props.hideBottom && tableElement.getElementsByClassName('q-table__bottom').length > 0) {
tableElement.getElementsByClassName('q-table__bottom')[0].style.setProperty('--tableBottomHeight', 50 + 'px');
tableElement.getElementsByClassName('q-table__bottom')[0].style.setProperty('--tableBottomButtonHeight', 40 + 'px');
}
});
if (this.props.title || this.props.toolbarActions.length > 0 || this.props.configButton) {
tableElement.getElementsByClassName('q-table__top')[0].style.setProperty('--tableTopPadding', '8px');
} else {
tableElement.getElementsByClassName('q-table__top')[0].style.setProperty('--tableTopPadding', '0px');
}
}
}

339
io.sc.platform.core.frontend/src/platform/components/grid/ts/function/RequestApi.ts

@ -0,0 +1,339 @@
import { $t, axios, noErrorAxios, NotifyManager, Tools, TreeBuilder, ServerExceptionHandler } from '@/platform';
import { toRaw } from 'vue';
import { Base } from '../Base';
import { Constant, PropsType, TableType } from '../index';
/**
* w-grid API函数
*/
export class RequestApi extends Base {
constructor(props: PropsType, table: TableType) {
super(props, table);
this.fetchData = this.fetchData.bind(this);
this.treeFetchDataByForeignKey = this.treeFetchDataByForeignKey.bind(this);
this.save = this.save.bind(this);
this.updates = this.updates.bind(this);
this.remove = this.remove.bind(this);
this.requestHandle = this.requestHandle.bind(this);
this.responseHandle = this.responseHandle.bind(this);
this.setExpandDatas = this.setExpandDatas.bind(this);
this.setTreeExpandDatas = this.setTreeExpandDatas.bind(this);
this.setAlongGroupExpandDatas = this.setAlongGroupExpandDatas.bind(this);
this.getFetchUrl = this.getFetchUrl.bind(this);
}
/**
*
* @param ops
*/
async fetchData(ops: any) {
this.tools?.editFM.exitInlineEdit();
this.table.store.loading = true;
const resp: any = await this.requestHandle(ops);
this.table.store.loading = false;
// 处理返回的结果
this.responseHandle(resp, ops);
this.tools?.dataFM.setExtraProperty(this.table.rows, ops);
this.tools?.opFM.resetHeaderCheckbox();
this.tools?.opFM.resetStyleVariableValue(100);
// 判断数据加载后是否展开树形表格
if (this.table.store.expand) {
if (this.props.treeLazyLoad) {
this.table.store.alreadyLoadAllData = true;
this.table.store.loading = true;
const datas = await this.tools?.reqApiFM.treeFetchDataByForeignKey();
this.tools?.apiFM.localMode.setLocalData(datas);
this.table.store.loading = false;
this.tools?.apiFM.operator.expand(undefined, true);
} else {
this.tools?.apiFM.operator.expand();
}
} else if (this.props.tree && this.props.treeLazyLoad && this.table.store.expandDatas.length > 0) {
for (let i = 0; i < this.table.store.expandDatas.length; i++) {
const primaryKey = this.table.store.expandDatas[i];
if (!this.table.store.lazyloadNoChildrenDatas.includes(primaryKey)) {
const childrenData = await this.tools?.reqApiFM.treeFetchDataByForeignKey(primaryKey);
if (childrenData.length > 0) {
this.tools?.apiFM.localMode.addLocalData(childrenData);
} else {
const row = this.tools?.dataFM.getRow(this.table.rows, primaryKey, false);
if (row) {
row[Constant.FIELD_NAMES.LAZYLOAD_NO_CHILDREN] = true;
}
}
} else {
const row = this.tools?.dataFM.getRow(this.table.rows, primaryKey, false);
if (row) {
row[Constant.FIELD_NAMES.LAZYLOAD_NO_CHILDREN] = true;
}
}
}
}
// 触发请求完成后事件
this.tools?.em.afterRequestData(toRaw(this.table.rows));
}
/**
*
* @param primaryKey
*/
async treeFetchDataByForeignKey(primaryKey?: string) {
const url = this.getFetchUrl();
const urlSearchParams = new URLSearchParams({ pageable: false });
if (primaryKey) {
const criteria = {
fieldName: this.props.foreignKey,
operator: Constant.CRITERIA_OPERATOR.equals,
value: primaryKey,
};
urlSearchParams.append('criteria', JSON.stringify(criteria));
}
const resp = await axios.get(url, { params: urlSearchParams }).catch((error) => {
console.error('[w-grid]fetch data error:', error);
this.table.store.loading = false;
});
let resultData: any = [];
if (resp && resp.data) {
const responseData = resp.data;
if (Array.isArray(responseData)) {
resultData = responseData;
} else if (typeof responseData === 'object' && responseData.content) {
resultData = responseData.content;
}
}
return resultData;
}
/**
*
* @param method
* @param data
* @param url url
* @param formRef form对象
* @param callback
* @param errorCallback
*/
save(method: string, data: any, url: string, formRef: any, callback: any, errorCallback: any) {
const requestParams = {
method: method,
headers: { 'content-type': 'application/json;charset=utf-8;' },
data: data,
url: url,
};
noErrorAxios(requestParams)
.then((resp) => {
if (callback) {
callback(resp);
}
NotifyManager.info($t('tip.operationSuccess') || '');
this.tools?.em.afterEditorDataSubmit({ grid: this.instance, data: resp.data });
})
.catch((error) => {
if (error?.code === 1001) {
// 验证错误
formRef?.setValidationErrors(error.data);
} else {
ServerExceptionHandler.handle(error);
}
if (errorCallback) {
errorCallback(error);
}
});
}
/**
*
* @param data
* @param callback
*/
updates(data: Array<any>, callback: any = undefined) {
const requestParams = {
method: 'PUT',
headers: { 'content-type': 'application/json;charset=utf-8;' },
data: data,
url: this.table?.url.dataUrl + '/updates',
};
noErrorAxios(requestParams)
.then((resp) => {
if (!Tools.isEmpty(callback)) {
callback(resp?.data);
}
})
.catch((error) => {
if (error?.code === 1001) {
// 验证错误
NotifyManager.error('服务器验证未通过');
} else {
ServerExceptionHandler.handle(error);
}
});
}
/**
*
* @param ids
*/
remove(ids: Array<any>) {
const requestParams: any = {
method: 'DELETE',
url: this.table.url.removeDataUrl || this.table.url.dataUrl,
data: ids,
};
axios(requestParams)
.then((resp) => {
// 触发请求完成后事件
this.tools?.em.afterRemove(resp?.data);
NotifyManager.info($t('tip.operationSuccess') || '');
if (this.props.refreshData || !this.props.tree) {
this.tools?.apiFM.operator.refreshGrid();
} else {
this.tools?.apiFM.localMode.removeLocalData(resp?.data);
}
})
.catch((error) => {
console.error('[w-grid]Remove error:', error);
});
}
private async requestHandle(ops: any) {
const reqParams: any = {};
if (this.props.pageable && !this.props.tree && !this.props.localMode) {
reqParams.page = ops.pagination.page;
reqParams.size = ops.pagination.rowsPerPage;
}
reqParams.pageable = this.props.tree || this.props.localMode ? false : this.props.pageable;
if (ops.pagination.sortBy && ops.pagination.sortBy !== '') {
if (ops.pagination.descending) {
reqParams.sortBy = '-' + ops.pagination.sortBy;
reqParams.descending = ops.pagination.descending;
} else {
reqParams.sortBy = ops.pagination.sortBy;
}
} else if (this.props.sortBy && this.props.sortBy.length > 0) {
reqParams.sortBy = this.props.sortBy;
}
// 后台 RestCrudController 查询
let urlSearchParams = this.tools?.criteriaFM.buildURLSearchParams(reqParams);
// 请求前事件触发
const _requestParams = await this.tools?.em.beforeRequestData(urlSearchParams);
if (_requestParams) {
urlSearchParams = _requestParams;
}
const url = this.getFetchUrl();
// 树型表格懒加载
if (this.props.tree && this.props.treeLazyLoad && urlSearchParams) {
const criteria = {
fieldName: this.props.foreignKey,
operator: Constant.CRITERIA_OPERATOR.isNull,
};
urlSearchParams.append('criteria', JSON.stringify(criteria));
}
const resp = await axios.get(url, { params: urlSearchParams }).catch((error) => {
console.error('[w-grid]fetch data error:', error);
this.table.store.loading = false;
});
return resp;
}
private responseHandle(resp: any, ops: any) {
if (resp && resp.data) {
if (!this.table.store.expand) {
// 记录当前已展开记录
this.setExpandDatas();
}
const responseData = resp.data;
if (Array.isArray(responseData)) {
this.table.rows = responseData;
this.table.store.pagination.rowsNumber = responseData.length;
} else if (typeof responseData === 'object' && responseData.content) {
if (this.props.pageable) {
this.table.store.pagination.page = this.table.store.pagination.reqPageStart === 0 ? responseData.number + 1 : responseData.number;
this.table.store.pagination.rowsPerPage = responseData.size || this.table.store.pagination.rowsPerPage;
}
this.table.store.pagination.rowsNumber = responseData.totalElements;
this.table.rows = responseData.content;
} else {
this.table.rows = [];
}
if (this.props.tree) {
if (Array.isArray(responseData)) {
this.table.store.pagination.rowsNumber = responseData.length;
this.table.store.pagination.rowsPerPage = 0;
this.table.rows =
this.props.treeRelationship === 'parent' ? TreeBuilder.build(responseData, this.props.foreignKey, this.props.primaryKey) : responseData;
} else if (typeof responseData === 'object' && responseData.content) {
this.table.store.pagination.rowsNumber = responseData.content.length;
this.table.store.pagination.rowsPerPage = 0;
this.table.rows =
this.props.treeRelationship === 'parent'
? TreeBuilder.build(responseData.content, this.props.foreignKey, this.props.primaryKey)
: responseData.content;
}
}
} else {
this.table.rows = [];
}
this.table.store.pagination.sortBy = ops.pagination.sortBy;
this.table.store.pagination.descending = ops.pagination.descending;
}
/**
* store
* @param
*/
private setExpandDatas() {
if (this.props.tree) {
this.table.store.expandDatas = [];
this.table.store.lazyloadNoChildrenDatas = [];
this.setTreeExpandDatas(toRaw(this.table.rows));
} else if (this.props.groupMode === Constant.GROUP_MODE.ALONE) {
this.setAlongGroupExpandDatas();
}
}
private setTreeExpandDatas(array: Array<any>) {
array.forEach((item) => {
if (item[Constant.FIELD_NAMES.EXPAND]) {
if (this.props.treeLazyLoad) {
const parents = this.tools?.apiFM.getData.getCascadeParentsData(item, null);
if (parents.length === 0 || parents.findIndex((parent) => !parent[Constant.FIELD_NAMES.EXPAND]) === -1) {
this.table.store.expandDatas.push(item[this.props.primaryKey]);
}
} else {
this.table.store.expandDatas.push(item[this.props.primaryKey]);
}
}
if (item[Constant.FIELD_NAMES.LAZYLOAD_NO_CHILDREN] && (!item.children || item.children.length === 0)) {
this.table.store.lazyloadNoChildrenDatas.push(item[this.props.primaryKey]);
}
if (item.children?.length > 0) {
this.setTreeExpandDatas(item.children);
}
});
}
private setAlongGroupExpandDatas() {
this.table.configStore.aloneGroupRecords.forEach((item) => {
if (item['expand'] && !this.table.store.expandDatas.includes(item['groupName'])) {
this.table.store.expandDatas.push(item['groupName']);
} else if (!item['expand']) {
const index = this.table.store.expandDatas.findIndex((tmp) => tmp === item['groupName']);
if (index > -1) {
this.table.store.expandDatas.splice(index, 1);
}
}
});
}
private getFetchUrl() {
return this.table?.url.fetchDataUrl || this.table?.url.dataUrl || '';
}
}

440
io.sc.platform.core.frontend/src/platform/components/grid/ts/function/RowData.ts

@ -0,0 +1,440 @@
import { toRaw } from 'vue';
import sortArray from 'sort-array';
import { Tools } from '@/platform';
import { Base } from '../Base';
import { Constant, GridTools, PropsType, TableType } from '../index';
/**
*
*/
export class RowData extends Base {
private aloneGroupRowIndex: number = 0;
constructor(props: PropsType, table: TableType) {
super(props, table);
this.getRow = this.getRow.bind(this);
this.getRowsByTarget = this.getRowsByTarget.bind(this);
this.isSelectedRow = this.isSelectedRow.bind(this);
this.isSelectedCell = this.isSelectedCell.bind(this);
this.setOldValue = this.setOldValue.bind(this);
this.isLastRow = this.isLastRow.bind(this);
this.checkLastRow = this.checkLastRow.bind(this);
this.setExtraProperty = this.setExtraProperty.bind(this);
this.arrayOrderBy = this.arrayOrderBy.bind(this);
this.mergeSortFields = this.mergeSortFields.bind(this);
this.mergeDefaultSortBy = this.mergeDefaultSortBy.bind(this);
this.rowDataExtraPropertyHandle = this.rowDataExtraPropertyHandle.bind(this);
this.mergeGroupBy = this.mergeGroupBy.bind(this);
this.aloneGroupBy = this.aloneGroupBy.bind(this);
this.setRowIndex = this.setRowIndex.bind(this);
this.initRowDataExtraProperty = this.initRowDataExtraProperty.bind(this);
this.getInitRowTicked = this.getInitRowTicked.bind(this);
this.getInitRowSelected = this.getInitRowSelected.bind(this);
this.pushRow = this.pushRow.bind(this);
this.rowsKeyIsEmptyError = this.rowsKeyIsEmptyError.bind(this);
this.rowsKeyNotExistError = this.rowsKeyNotExistError.bind(this);
}
/**
*
* @param array
* @param key
* @param parentFlag key为父ID标识key为父ID时 fieldName
* @param fieldName
* @returns
*/
public getRow(array: Array<any>, key: string, parentFlag: boolean, fieldName: string = Constant.FIELD_NAMES.ROW_KEY): any {
let result = undefined;
for (let i = 0; i < array.length; i++) {
const item = array[i];
if (parentFlag ? item[this.props.primaryKey] === key : item[fieldName] === key || item[this.props.primaryKey] === key) {
result = item;
break;
} else if (this.props.tree && item.children && item.children.length > 0) {
const temp = this.getRow(item.children, key, parentFlag);
if (temp) {
result = temp;
}
}
}
return result;
}
/**
*
* @param rows
* @param target
* @returns
*/
public getRowsByTarget(rows: Array<any>, target: any) {
const result = [];
if (target && Array.isArray(target) && target.length > 0) {
target.forEach((item) => {
this.rowsKeyIsEmptyError(item);
this.pushRow(rows, result, item);
});
} else if (!Tools.isEmpty(target)) {
this.pushRow(rows, result, target);
}
return result;
}
/**
*
* @param rowKey
*/
public isSelectedRow(rowKey: string) {
if (this.table && !Tools.isEmpty(rowKey)) {
const selected = this.tools?.apiFM.getData.getSelectedRow();
if (selected) {
return rowKey === selected[Constant.FIELD_NAMES.ROW_KEY];
}
}
return false;
}
/**
*
* @param rowKey
* @param colName
*/
public isSelectedCell(rowKey: string, colName: string) {
if (this.table && !Tools.isEmpty(rowKey) && !Tools.isEmpty(colName)) {
const selected = this.tools?.apiFM.getData.getSelectedCell();
if (selected?.colName && rowKey === selected['row'][Constant.FIELD_NAMES.ROW_KEY] && colName === selected.colName) {
return true;
}
}
return false;
}
/**
*
* @param row
*/
public setOldValue(row) {
const values = Object.values(Constant.FIELD_NAMES.getExtraFields(this.props));
Object.keys(row).forEach((key) => {
if (
values.findIndex((item) => {
return item === key;
}) < 0
) {
row[Constant.FIELD_NAMES.ROW_OLD_VALUE][key] = toRaw(row[key]);
}
});
}
/**
*
* @param rows
* @returns
*/
public async setExtraProperty(rows: Array<any>, ops?: any, lazyloadNoChildren: boolean = false) {
// 清空分组记录
this.table.configStore.aloneGroupRecords = [];
// 重置分组行下标
this.aloneGroupRowIndex = 0;
if (this.props.groupMode === Constant.GROUP_MODE.MERGE && !this.props.tree) {
// 如果存在需要合并的字段,并且不是树表格,重新对数据进行排序
this.table.store.mergeGroupRecords = {};
const groupByFields = this.tools?.cm.mergeGroupByField.value;
this.arrayOrderBy(rows, groupByFields, ops);
}
this.rowDataExtraPropertyHandle(rows, lazyloadNoChildren);
}
/**
*
* @param row
*/
isLastRow(row: any) {
if (this.tools?.table?.rows?.length === 0) {
return true;
}
const lastRow = this.tools?.table.rows[this.tools.table.rows.length - 1];
return this.checkLastRow(lastRow, row);
}
private checkLastRow(lastRow: any, row: any) {
if (this.tools?.props.tree && row[Constant.FIELD_NAMES.EXPAND] && !Tools.isEmpty(row.children) && row.children.length > 0) {
const childrenLastRow = row.children[row.children.length - 1];
if (childrenLastRow[Constant.FIELD_NAMES.EXPAND] && !Tools.isEmpty(childrenLastRow.children) && childrenLastRow.children.length > 0) {
return this.checkLastRow(lastRow, childrenLastRow);
} else {
return childrenLastRow[Constant.FIELD_NAMES.ROW_KEY] === row[Constant.FIELD_NAMES.ROW_KEY];
}
} else {
return lastRow[Constant.FIELD_NAMES.ROW_KEY] === row[Constant.FIELD_NAMES.ROW_KEY];
}
}
/**
*
* @param array
* @param sortFields
*/
private arrayOrderBy(array: Array<any>, sortFields: any, ops: any) {
let fields = [...sortFields];
// 将分组字段与排序字段进行合并
fields = this.mergeSortFields(fields, ops);
const customOrders = {};
// 对每一个字段进行排序
for (const field of fields) {
const desc = field.startsWith('-');
const _field = desc ? field.substring(1) : field;
const fieldSortArr = array.map((item) => item[_field]);
sortArray(fieldSortArr, { order: desc ? 'desc' : 'asc' });
customOrders[_field] = fieldSortArr;
}
const byFields = fields.map((item) => (item.startsWith('-') ? item.substring(1) : item));
sortArray(array, {
by: byFields,
order: byFields,
customOrders: customOrders,
});
}
private mergeSortFields(fields: Array<any>, ops: any) {
const sortBy = ops?.pagination?.sortBy;
if (sortBy) {
const prefix = ops.pagination.descending ? '-' : '';
if (fields.findIndex((tmp) => tmp === sortBy) === -1) {
fields.push(prefix + sortBy);
} else {
fields = fields.map((tmp) => {
if (tmp === sortBy) {
return prefix + sortBy;
}
return tmp;
});
}
return fields;
} else {
return this.mergeDefaultSortBy(fields);
}
}
private mergeDefaultSortBy(fields: Array<any>) {
if (Array.isArray(this.tools?.props.sortBy) && this.tools?.props.sortBy.length > 0) {
this.tools.props.sortBy.forEach((item) => {
if (item.startsWith('-')) {
if (fields.findIndex((tmp) => tmp === item.substring(1)) === -1) {
fields.push(item);
} else {
fields = fields.map((tmp) => {
if (tmp === item.substring(1)) {
return item;
}
return tmp;
});
}
} else {
if (fields.findIndex((tmp) => tmp === item) === -1) {
fields.push(item);
}
}
});
}
return fields;
}
private rowDataExtraPropertyHandle(rows: Array<any>, lazyloadNoChildren: boolean = false) {
if (rows && rows.length > 0) {
rows.forEach((item: any, index) => {
this.initRowDataExtraProperty(item, lazyloadNoChildren);
this.mergeGroupBy(item);
this.aloneGroupBy(item);
if (item[this.props.tickedField] === true) {
item[Constant.FIELD_NAMES.TICKED_COUNT] = 1;
} else if (item[Constant.FIELD_NAMES.TICKED_COUNT] === false) {
item[Constant.FIELD_NAMES.TICKED_COUNT] = 0;
} else {
item[Constant.FIELD_NAMES.TICKED_COUNT] = 0;
}
if (this.props.tree && item.children && item.children.length > 0) {
this.rowDataExtraPropertyHandle(item.children, lazyloadNoChildren);
item[Constant.FIELD_NAMES.CHILDREN_TICKED_COUNT] = 0;
item.children.forEach((child) => {
item[Constant.FIELD_NAMES.CHILDREN_TICKED_COUNT] += child[Constant.FIELD_NAMES.TICKED_COUNT];
});
if (item[Constant.FIELD_NAMES.CHILDREN_TICKED_COUNT] === 0) {
item[this.props.tickedField] = false;
item[Constant.FIELD_NAMES.TICKED_COUNT] = 0;
} else if (item[Constant.FIELD_NAMES.CHILDREN_TICKED_COUNT] === item.children.length) {
item[this.props.tickedField] = true;
item[Constant.FIELD_NAMES.TICKED_COUNT] = 1;
} else {
item[this.props.tickedField] = null;
item[Constant.FIELD_NAMES.TICKED_COUNT] = 0;
}
}
});
}
}
/**
*
* @param rowData
*/
private mergeGroupBy(rowData: any) {
if (this.props.groupMode === Constant.GROUP_MODE.MERGE && !this.props.tree) {
const groupByFields = this.tools?.cm.mergeGroupByField.value;
if (groupByFields && Array.isArray(groupByFields)) {
groupByFields.forEach((columnName: any) => {
const tmpArr = [rowData[Constant.FIELD_NAMES.ROW_KEY]];
if (this.table.store.mergeGroupRecords[columnName]) {
if (this.table.store.mergeGroupRecords[columnName][rowData[columnName]]) {
this.table.store.mergeGroupRecords[columnName][rowData[columnName]].push(rowData[Constant.FIELD_NAMES.ROW_KEY]);
} else {
this.table.store.mergeGroupRecords[columnName][rowData[columnName]] = tmpArr;
}
} else {
this.table.store.mergeGroupRecords[columnName] = { [rowData[columnName]]: tmpArr };
}
});
}
}
}
/**
*
*/
private aloneGroupBy(rowData: any) {
if (this.props.groupMode === Constant.GROUP_MODE.ALONE) {
const groupByField = this.tools?.table.configStore.aloneGroupByField;
if (groupByField) {
const expand = this.getAloneGroupDefaultExpand(rowData, groupByField);
const findResult = this.table.configStore.aloneGroupRecords.find((tmp) => tmp['groupName'] === rowData[groupByField]);
if (!findResult) {
this.table.configStore.aloneGroupRecords.push({ groupName: rowData[groupByField], expand: expand, rows: [rowData] });
} else {
findResult.rows.push(rowData);
}
this.setRowIndex(rowData);
}
}
}
/**
*
* @param rowData
* @param groupByField
* @returns
*/
private getAloneGroupDefaultExpand(rowData: any, groupByField: string) {
let expand = false;
if (typeof this.props.groupStartOpen === 'string' && this.props.groupStartOpen === Constant.GROUP_START_OPEN.ALL) {
expand = true;
} else if (
typeof this.props.groupStartOpen === 'string' &&
this.props.groupStartOpen === Constant.GROUP_START_OPEN.FIRST &&
this.aloneGroupRowIndex === 0
) {
expand = true;
} else if (Array.isArray(this.props.groupStartOpen) && this.props.groupStartOpen.includes(rowData[groupByField])) {
expand = true;
} else if (typeof this.props.groupStartOpen === 'function') {
expand = this.props.groupStartOpen({
groupByField: rowData[groupByField],
row: rowData,
});
}
// 当前展开记录中已经有,强制改为展开状态
if (this.table.store.expandDatas.length > 0 && this.table.store.expandDatas.includes(rowData[groupByField])) {
expand = true;
}
return expand;
}
/**
*
* @param rowData
*/
private setRowIndex(rowData: any) {
rowData[Constant.FIELD_NAMES.ROW_INDEX] = this.aloneGroupRowIndex;
this.aloneGroupRowIndex++;
}
/**
*
* @param rowData
*/
private async initRowDataExtraProperty(rowData: any, lazyloadNoChildren: boolean = false) {
rowData[Constant.FIELD_NAMES.ROW_KEY] = Tools.uuid();
rowData[this.props.tickedField] = this.getInitRowTicked(rowData);
rowData[this.props.selectedField] = this.getInitRowSelected(rowData);
rowData[Constant.FIELD_NAMES.ROW_OLD_VALUE] = {};
this.setOldValue(rowData);
if (this.props.tree) {
if (this.table.store.expandDatas.length > 0 && this.table.store.expandDatas.includes(rowData[this.props.primaryKey])) {
rowData[Constant.FIELD_NAMES.EXPAND] = true;
} else if (Tools.isEmpty(rowData[Constant.FIELD_NAMES.EXPAND])) {
rowData[Constant.FIELD_NAMES.EXPAND] = false;
}
if (lazyloadNoChildren && (!rowData.children || rowData.children.length === 0)) {
rowData[Constant.FIELD_NAMES.LAZYLOAD_NO_CHILDREN] = true;
}
}
}
/**
* ticked状态
* @param rowData
* @returns
*/
private getInitRowTicked(rowData: any) {
if (rowData[this.props.tickedField]) {
return true;
} else if (Object.keys(this.props.tickedRecord).length > 0 && this.props.tickedRecord['data']?.length > 0) {
const columnName = this.props.tickedRecord['columnName'] || this.props.primaryKey;
const data = this.props.tickedRecord['data'];
if (
data.findIndex((item) => {
return item === rowData[columnName];
}) > -1
) {
return true;
}
}
return false;
}
/**
* selected状态
* @param rowData
* @returns
*/
private getInitRowSelected(rowData: any) {
if (rowData[this.props.selectedField]) {
return true;
} else if (!this.props.tree && rowData[this.props.tickedField]) {
return true;
} else if (this.props.tree && !this.props.checkboxSelection && rowData[this.props.tickedField]) {
return true;
} else {
return false;
}
}
private pushRow(rows: Array<any>, result: Array<any>, item: any) {
const row = this.getRow(
rows,
typeof item === 'object' ? item[this.props.primaryKey] || item[Constant.FIELD_NAMES.ROW_KEY] : item,
false,
item[this.props.primaryKey] ? this.props.primaryKey : Constant.FIELD_NAMES.ROW_KEY,
);
if (row) {
result.push(row);
} else {
this.rowsKeyNotExistError(typeof item === 'object' ? item[this.props.primaryKey] || item[Constant.FIELD_NAMES.ROW_KEY] : item);
}
}
private rowsKeyIsEmptyError(data: any) {
if (typeof data === 'object' && Tools.isEmpty(data[this.props.primaryKey]) && Tools.isEmpty(data[Constant.FIELD_NAMES.ROW_KEY])) {
throw new Error('[w-grid]Data does not contain `primaryKey` or `rowKey`.');
}
}
private rowsKeyNotExistError(data: any) {
throw new Error('[w-grid]`' + data + '` Not Found in Data Row.');
}
}

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

@ -1,105 +0,0 @@
import { Tools, t } from '@/platform';
// 拖拽排序模式
export const dndMode = {
local: 'local', // 本地排序
server: 'server', // 服务端排序
};
export const dndImage = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' %3E%3Cpath /%3E%3C/svg%3E`;
// 选择模式
export const selectMode = {
none: 'none', // 不允许选择
row: 'row', // 行选择
cell: 'cell', // 单元格选择
};
// 分组模式
export const groupMode = {
alone: 'alone', // 单独行进行分组
merge: 'merge', // 合并行进行分组
};
// 表格内容区域快速编辑状态
export const editStatus = {
none: 'none', // 不在编辑状态
cell: 'cellEdit', // 单元格编辑状态
row: 'rowEdit', // 行编辑状态
rows: 'rowsEdit', // 所有行编辑状态
};
// 内置编辑窗口中form的状态
export const formStatus = {
add: 'add', // 新增
edit: 'edit', // 编辑
clone: 'clone', // 复制
addTop: 'addTop', // 新增顶级节点
addChild: 'addChild', // 新增子节点
};
// 列样式处理
const columnStyle = (item: any) => {
let style = '';
if (Tools.hasOwnProperty(item, 'style')) {
style = item.style;
}
if (Tools.hasOwnProperty(item, 'width')) {
if (typeof item.width === 'number') {
item.style = `min-width: ` + item.width + `px; width: ` + item.width + `px;max-width: ` + item.width + `px;` + style;
} else {
item.style = `min-width: ` + item.width + `; width: ` + item.width + `;max-width: ` + item.width + `;` + style;
}
delete item.width;
if (Tools.hasOwnProperty(item, 'classes')) {
item.classes = item.classes + ' truncate';
} else {
item.classes = 'truncate';
}
}
};
// 孩子列处理
const childrenHandler = (item: any, gridColumns: any) => {
if (item.columns && item.columns.length > 0) {
item.columns.forEach((column) => {
childrenHandler(column, gridColumns);
});
} else {
columnStyle(item);
const col = {
...{ align: 'left', label: item.name, field: item.name, name: item.name, sortable: true, showIf: true },
...item,
};
if (Tools.isEmpty(col.name)) {
col.name = Tools.uuid();
}
gridColumns.push(col);
}
};
// 列的默认属性
export const columnDefaultProps = (columns: any, sortNo: boolean) => {
const gridColumns = <any>[];
if (columns && columns.length > 0) {
gridColumns.push({ name: '_sortNo_', align: 'center', label: t('rownum'), field: '_sortNo_', showIf: sortNo });
columns.forEach((item: any) => {
childrenHandler(item, gridColumns);
});
return gridColumns;
}
return [];
};
export const sortByProperties = (arr, properties) => {
for (const prop of properties) {
arr.sort((a, b) => {
if (a[prop] < b[prop]) {
return -1;
} else if (a[prop] > b[prop]) {
return 1;
}
return 0;
});
}
};

14
io.sc.platform.core.frontend/src/platform/components/grid/ts/index.ts

@ -0,0 +1,14 @@
import { Constant } from './constant/Constant';
import { EventManager } from './event/EventManager';
import { ExposeApiManager } from './expose-api/ExposeApiManager';
import { Init } from './Init';
import type { TableType } from './types/TableType';
import type { EventType } from './types/EventType';
import type { PropsType } from './types/PropsType';
import type { UrlType } from './types/table/UrlType';
import { GridTools } from './GridTools';
export { Constant, EventManager, ExposeApiManager, Init, EventType, PropsType, UrlType, TableType, GridTools };

55
io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/Button.ts

@ -0,0 +1,55 @@
import { Tools } from '@/platform';
import { Loading } from 'quasar';
import { nextTick } from 'vue';
import { Base } from '../Base';
/**
* w-grid
*/
export abstract class Button extends Base {
/**
*
*/
abstract name: string;
/**
* toolbar中的配置属性
*/
abstract getButtonConfig(args: any): object | string;
/**
* editor窗口
* @param grid
* @param title
* @param status Form的状态
* @param data undefined
*/
openEditor(grid: any, title: string, status: string, data: any) {
grid.getEditorDialog().show();
nextTick(() => {
grid.getEditorDialog().setTitle(title);
grid.getEditorForm().setStatus(status);
if (!Tools.isEmpty(data)) {
grid.getEditorForm().setData(data);
}
});
}
/**
* /
* @param msg
*/
showLoading(msg: string = '正在处理,请稍等...') {
Loading.show({
message: msg,
boxClass: 'bg-grey-2 text-grey-9',
spinnerColor: 'primary',
});
}
/**
* /
*/
hideLoading() {
Loading.hide();
}
}

227
io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/ButtonManager.ts

@ -0,0 +1,227 @@
import { Tools } from '@/platform';
import { GridTools } from '../GridTools';
import { PropsType } from '../types/PropsType';
import { TableType } from '../types/TableType';
import { Add } from './buttons/Add';
import { AddChild } from './buttons/AddChild';
import { AddTop } from './buttons/AddTop';
import { CellEdit } from './buttons/CellEdit';
import { Clone } from './buttons/Clone';
import { Edit } from './buttons/Edit';
import { Expand } from './buttons/Expand';
import { Export } from './buttons/Export';
import { InlineCellEdit } from './buttons/InlineCellEdit';
import { InlineRowEdit } from './buttons/InlineRowEdit';
import { InlineRowsEdit } from './buttons/InlineRowsEdit';
import { MoreQuery } from './buttons/MoreQuery';
import { Query } from './buttons/Query';
import { AdvancedQuery } from './buttons/AdvancedQuery';
import { Refresh } from './buttons/Refresh';
import { Remove } from './buttons/Remove';
import { Reset } from './buttons/Reset';
import { ResetDefaultValues } from './buttons/ResetDefaultValues';
import { Separator } from './buttons/Separator';
import { View } from './buttons/view';
import { FullScreen } from './buttons/FullScreen';
/**
* w-grid
*/
export class ButtonManager {
// 查询
query: Query;
moreQuery: MoreQuery;
advancedQuery: AdvancedQuery;
reset: Reset;
refresh: Refresh;
// 新增
add: Add;
addTop: AddTop;
addChild: AddChild;
// 编辑
edit: Edit;
cellEdit: CellEdit;
inlineCellEdit: InlineCellEdit;
inlineRowEdit: InlineRowEdit;
inlineRowsEdit: InlineRowsEdit;
clone: Clone;
// 删除
remove: Remove;
// 其他
fullScreen: FullScreen;
separator: Separator;
view: View;
export: Export;
expand: Expand;
resetDefaultValues: ResetDefaultValues;
constructor(props: PropsType, table: TableType) {
this.query = new Query(props, table);
this.moreQuery = new MoreQuery(props, table);
this.advancedQuery = new AdvancedQuery(props, table);
this.reset = new Reset(props, table);
this.refresh = new Refresh(props, table);
this.add = new Add(props, table);
this.addTop = new AddTop(props, table);
this.addChild = new AddChild(props, table);
this.edit = new Edit(props, table);
this.cellEdit = new CellEdit(props, table);
this.inlineCellEdit = new InlineCellEdit(props, table);
this.inlineRowEdit = new InlineRowEdit(props, table);
this.inlineRowsEdit = new InlineRowsEdit(props, table);
this.clone = new Clone(props, table);
this.remove = new Remove(props, table);
this.fullScreen = new FullScreen(props, table);
this.separator = new Separator(props, table);
this.view = new View(props, table);
this.export = new Export(props, table);
this.expand = new Expand(props, table);
this.resetDefaultValues = new ResetDefaultValues(props, table);
}
/**
*
* @param name `{ 按钮名称:按钮配置 }`
* @returns
*/
getButtonConfig(name?: string) {
let config = {};
if (Tools.isEmpty(name)) {
config = {
[this.query.name]: this.query.getButtonConfig(),
[this.moreQuery.name]: this.moreQuery.getButtonConfig(),
[this.advancedQuery.name]: this.advancedQuery.getButtonConfig(),
[this.reset.name]: this.reset.getButtonConfig(),
[this.refresh.name]: this.refresh.getButtonConfig(),
[this.add.name]: this.add.getButtonConfig(),
[this.addTop.name]: this.addTop.getButtonConfig(),
[this.addChild.name]: this.addChild.getButtonConfig(),
[this.edit.name]: this.edit.getButtonConfig(),
[this.cellEdit.name]: this.cellEdit.getButtonConfig(),
[this.inlineCellEdit.name]: this.inlineCellEdit.getButtonConfig(),
[this.inlineRowEdit.name]: this.inlineRowEdit.getButtonConfig(),
[this.inlineRowsEdit.name]: this.inlineRowsEdit.getButtonConfig(),
[this.clone.name]: this.clone.getButtonConfig(),
[this.remove.name]: this.remove.getButtonConfig(),
[this.fullScreen.name]: this.fullScreen.getButtonConfig(),
[this.separator.name]: this.separator.getButtonConfig(),
[this.view.name]: this.view.getButtonConfig(),
[this.export.name]: this.export.getButtonConfig(),
[this.expand.name]: this.expand.getButtonConfig(),
[this.resetDefaultValues.name]: this.resetDefaultValues.getButtonConfig(),
};
} else {
switch (name) {
case this.query.name:
config = this.query.getButtonConfig();
break;
case this.moreQuery.name:
config = this.moreQuery.getButtonConfig();
break;
case this.advancedQuery.name:
config = this.advancedQuery.getButtonConfig();
break;
case this.reset.name:
config = this.reset.getButtonConfig();
break;
case this.refresh.name:
config = this.refresh.getButtonConfig();
break;
case this.add.name:
config = this.add.getButtonConfig();
break;
case this.addTop.name:
config = this.addTop.getButtonConfig();
break;
case this.addChild.name:
config = this.addChild.getButtonConfig();
break;
case this.edit.name:
config = this.edit.getButtonConfig();
break;
case this.cellEdit.name:
config = this.cellEdit.getButtonConfig();
break;
case this.inlineCellEdit.name:
config = this.inlineCellEdit.getButtonConfig();
break;
case this.inlineRowEdit.name:
config = this.inlineRowEdit.getButtonConfig();
break;
case this.inlineRowsEdit.name:
config = this.inlineRowsEdit.getButtonConfig();
break;
case this.clone.name:
config = this.clone.getButtonConfig();
break;
case this.remove.name:
config = this.remove.getButtonConfig();
break;
case this.fullScreen.name:
config = this.fullScreen.getButtonConfig();
break;
case this.separator.name:
config = this.separator.getButtonConfig();
break;
case this.view.name:
config = this.view.getButtonConfig();
break;
case this.export.name:
config = this.export.getButtonConfig();
break;
case this.expand.name:
config = this.expand.getButtonConfig();
break;
case this.resetDefaultValues.name:
config = this.resetDefaultValues.getButtonConfig();
break;
}
}
return config;
}
setInstance(instance: any, tools: GridTools) {
this.query.setInstance(instance, tools);
this.moreQuery.setInstance(instance, tools);
this.advancedQuery.setInstance(instance, tools);
this.reset.setInstance(instance, tools);
this.refresh.setInstance(instance, tools);
this.add.setInstance(instance, tools);
this.addTop.setInstance(instance, tools);
this.addChild.setInstance(instance, tools);
this.edit.setInstance(instance, tools);
this.cellEdit.setInstance(instance, tools);
this.inlineCellEdit.setInstance(instance, tools);
this.inlineRowEdit.setInstance(instance, tools);
this.inlineRowsEdit.setInstance(instance, tools);
this.clone.setInstance(instance, tools);
this.remove.setInstance(instance, tools);
this.fullScreen.setInstance(instance, tools);
this.separator.setInstance(instance, tools);
this.view.setInstance(instance, tools);
this.export.setInstance(instance, tools);
this.expand.setInstance(instance, tools);
this.resetDefaultValues.setInstance(instance, tools);
}
}

30
io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/buttons/Add.ts

@ -0,0 +1,30 @@
import { $t } from '@/platform';
import { Button } from '../Button';
import { Constant, PropsType, TableType } from '../../index';
export class Add extends Button {
name = 'add';
constructor(props: PropsType, table: TableType) {
super(props, table);
this.click = this.click.bind(this);
this.getButtonConfig = this.getButtonConfig.bind(this);
}
click(args) {
this.openEditor(args.grid, $t('action.addNew'), Constant.FORM_STATUS.ADD, undefined);
}
getButtonConfig() {
return {
name: this.name,
icon: 'add',
labelI18nKey: 'action.addNew',
label: $t('action.addNew'),
click: this.click,
afterEditorOpen: (args) => {
this.tools?.em.afterEditorOpen(undefined);
},
};
}
}

36
io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/buttons/AddChild.ts

@ -0,0 +1,36 @@
import { $t } from '@/platform';
import { Button } from '../Button';
import { Constant, PropsType, TableType } from '../../index';
export class AddChild extends Button {
name = 'addChild';
constructor(props: PropsType, table: TableType) {
super(props, table);
this.click = this.click.bind(this);
this.getButtonConfig = this.getButtonConfig.bind(this);
}
click(args) {
this.openEditor(args.grid, $t('action.addChild'), Constant.FORM_STATUS.ADD_CHILD, undefined);
}
getButtonConfig() {
return {
name: this.name,
icon: 'playlist_add',
labelI18nKey: 'action.addChild',
label: $t('action.addChild'),
enableIf: (args) => {
if (args.selected) {
return true;
}
return false;
},
click: this.click,
afterEditorOpen: (args) => {
this.tools?.em.afterEditorOpen(undefined);
},
};
}
}

30
io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/buttons/AddTop.ts

@ -0,0 +1,30 @@
import { $t } from '@/platform';
import { Button } from '../Button';
import { Constant, PropsType, TableType } from '../../index';
export class AddTop extends Button {
name = 'addTop';
constructor(props: PropsType, table: TableType) {
super(props, table);
this.click = this.click.bind(this);
this.getButtonConfig = this.getButtonConfig.bind(this);
}
click(args) {
this.openEditor(args.grid, $t('action.addTop'), Constant.FORM_STATUS.ADD_TOP, undefined);
}
getButtonConfig() {
return {
name: this.name,
icon: 'add',
labelI18nKey: 'action.addTop',
label: $t('action.addTop'),
click: (args) => {},
afterEditorOpen: (args) => {
this.tools?.em.afterEditorOpen(undefined);
},
};
}
}

34
io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/buttons/AdvancedQuery.ts

@ -0,0 +1,34 @@
import { $t } from '@/platform';
import { PropsType, TableType } from '../../index';
import { Button } from '../Button';
export class AdvancedQuery extends Button {
name = 'advancedQuery';
constructor(props: PropsType, table: TableType) {
super(props, table);
this.click = this.click.bind(this);
this.getButtonConfig = this.getButtonConfig.bind(this);
}
click(args) {
this.table.advancedQueryStatus = !this.table.advancedQueryStatus;
}
getButtonConfig() {
return {
name: this.name,
icon: 'manage_search',
labelI18nKey: 'action.advancedQuery',
label: $t('action.advancedQuery'),
enableIf: (args) => {
if (this.props.queryFormFields.length > 0 && this.props.advancedQuery) {
return true;
} else {
return false;
}
},
click: this.click,
};
}
}

45
io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/buttons/CellEdit.ts

@ -0,0 +1,45 @@
import { $t, NotifyManager, Tools } from '@/platform';
import { Button } from '../Button';
import { Constant, PropsType, TableType } from '../../index';
export class CellEdit extends Button {
name = 'cellEdit';
constructor(props: PropsType, table: TableType) {
super(props, table);
this.click = this.click.bind(this);
this.getButtonConfig = this.getButtonConfig.bind(this);
}
click(args) {
const selectedCell = args.grid.getSelectedCell();
if (args.grid.props.selectMode !== Constant.SELECT_MODE.CELL) {
console.warn('[w-grid]`selectMode` property is not `cell`, Cannot use cell editing function.');
return false;
} else if (Tools.isEmpty(selectedCell.colName)) {
NotifyManager.info('请选择要编辑的单元格');
return false;
} else if (!this.tools?.editFM.getEditorFieldByName(selectedCell.colName)) {
console.warn('[w-grid]The column selected is not configured with a component type for editing.');
return false;
}
// 弹出模态框编辑,但是只编辑该列的值
args.grid.getCellEditorDialog().show();
}
getButtonConfig() {
return {
name: this.name,
icon: 'border_color',
labelI18nKey: 'action.edit',
label: $t('action.edit'),
enableIf: (args) => {
if (!Tools.isEmpty(args.grid.getSelectedCell()['colName'])) {
return true;
}
return false;
},
click: this.click,
};
}
}

41
io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/buttons/Clone.ts

@ -0,0 +1,41 @@
import { $t, NotifyManager } from '@/platform';
import { Button } from '../Button';
import { Constant, PropsType, TableType } from '../../index';
export class Clone extends Button {
name = 'clone';
constructor(props: PropsType, table: TableType) {
super(props, table);
this.click = this.click.bind(this);
this.getButtonConfig = this.getButtonConfig.bind(this);
}
click(args) {
if (!args.selected) {
NotifyManager.warn($t('action.copy.tip'));
} else {
args.selected[args.grid.props.primaryKey] = undefined;
this.openEditor(args.grid, $t('action.copy'), Constant.FORM_STATUS.CLONE, args.selected);
}
}
getButtonConfig() {
return {
name: this.name,
icon: 'content_copy',
labelI18nKey: 'action.copy',
label: $t('action.copy'),
enableIf: (args) => {
if (args.selected) {
return true;
}
return false;
},
click: this.click,
afterEditorOpen: (args) => {
this.tools?.em.afterEditorOpen(args.selected);
},
};
}
}

40
io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/buttons/Edit.ts

@ -0,0 +1,40 @@
import { $t, NotifyManager } from '@/platform';
import { Button } from '../Button';
import { Constant, PropsType, TableType } from '../../index';
export class Edit extends Button {
name = 'edit';
constructor(props: PropsType, table: TableType) {
super(props, table);
this.click = this.click.bind(this);
this.getButtonConfig = this.getButtonConfig.bind(this);
}
click(args) {
if (!args.selected) {
NotifyManager.warn($t('action.edit.tip'));
} else {
this.openEditor(args.grid, $t('action.edit'), Constant.FORM_STATUS.EDIT, args.selected);
}
}
getButtonConfig() {
return {
name: this.name,
icon: 'edit',
labelI18nKey: 'action.edit',
label: $t('action.edit'),
enableIf: (args) => {
if (args.selected) {
return true;
}
return false;
},
click: this.click,
afterEditorOpen: (args) => {
this.tools?.em.afterEditorOpen(args.selected);
},
};
}
}

42
io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/buttons/Expand.ts

@ -0,0 +1,42 @@
import { $t } from '@/platform';
import { PropsType, TableType } from '../../index';
import { Button } from '../Button';
export class Expand extends Button {
name = 'expand';
constructor(props: PropsType, table: TableType) {
super(props, table);
this.click = this.click.bind(this);
this.getButtonConfig = this.getButtonConfig.bind(this);
}
async click(args) {
if (this.table.store.expand) {
this.tools?.apiFM.operator.collapse(undefined, true);
} else {
if (this.props.tree && this.props.treeLazyLoad && !this.table.store.alreadyLoadAllData) {
this.table.store.alreadyLoadAllData = true;
this.table.store.loading = true;
const datas = await this.tools?.reqApiFM.treeFetchDataByForeignKey();
this.tools?.apiFM.localMode.setLocalData(datas);
this.table.store.loading = false;
}
this.tools?.apiFM.operator.expand(undefined, true);
}
this.table.store.expand = !this.table.store.expand;
}
getButtonConfig() {
return {
name: this.name,
icon: () => {
return this.table.store.expand ? 'expand_less' : 'expand_more';
},
label: () => {
return this.table.store.expand ? $t('action.collapseAll') : $t('action.expandAll');
},
click: this.click,
};
}
}

89
io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/buttons/Export.ts

@ -0,0 +1,89 @@
import { $t, axios, NotifyManager, Tools } from '@/platform';
import { exportFile } from 'quasar';
import { PropsType, TableType } from '../../index';
import { Button } from '../Button';
export class Export extends Button {
name = 'export';
constructor(props: PropsType, table: TableType) {
super(props, table);
this.click = this.click.bind(this);
this.getExportData = this.getExportData.bind(this);
this.wrapCsvValue = this.wrapCsvValue.bind(this);
this.getButtonConfig = this.getButtonConfig.bind(this);
}
async click(args) {
this.showLoading();
let exportData = args.grid.getRows();
// 判断是否配置了 url, 以不分页形式请求后端获取全部数据一把导出。
if (!Tools.isEmpty(args.grid.props.fetchDataUrl) || !Tools.isEmpty(args.grid.props.dataUrl)) {
const fetchResult = await this.getExportData();
if (fetchResult && fetchResult.length > 0) {
exportData = fetchResult;
}
}
const content = [args.grid.props.columns.map((col) => this.wrapCsvValue(col.label))]
.concat(
exportData.map((row) =>
args.grid.props.columns
.map((col) =>
this.wrapCsvValue(typeof col.field === 'function' ? col.field(row) : row[col.field === void 0 ? col.name : col.field], col.format, row),
)
.join(','),
),
)
.join('\r\n');
const status = exportFile('table-export.csv', content, {
encoding: 'utf-8',
mimeType: 'text/csv',
byteOrderMark: '\uFEFF', // 解决乱码问题
});
if (status !== true) {
NotifyManager.error($t('action.export.failed'));
}
this.hideLoading();
}
getButtonConfig() {
return {
name: this.name,
icon: 'file_download',
labelI18nKey: 'action.export',
label: $t('action.export'),
click: this.click,
};
}
private async getExportData() {
let resultData = <any>[];
const reqParams: any = { pageable: false };
const urlSearchParams = this.tools?.criteriaFM.buildURLSearchParams(reqParams);
const resp = await axios.get(this.table?.url.fetchDataUrl || this.table.url.dataUrl, { params: urlSearchParams });
if (resp && resp.data) {
const responseData = resp.data;
if (Array.isArray(responseData)) {
resultData = responseData;
} else if (typeof responseData === 'object' && responseData.content) {
resultData = responseData.content;
}
}
return resultData;
}
private wrapCsvValue(val, formatFn, row) {
let formatted = formatFn !== void 0 ? formatFn(val, row) : val;
formatted = formatted === void 0 || formatted === null ? '' : String(formatted);
formatted = formatted.split('"').join('""');
/**
* Excel accepts \n and \r in strings, but some other CSV parsers do not
* Uncomment the next two lines to escape new lines
*/
// .split('\n').join('\\n')
// .split('\r').join('\\r')
return `"${formatted}"`;
}
}

30
io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/buttons/FullScreen.ts

@ -0,0 +1,30 @@
import { $t } from '@/platform';
import { PropsType, TableType } from '../../index';
import { Button } from '../Button';
export class FullScreen extends Button {
name = 'fullScreen';
constructor(props: PropsType, table: TableType) {
super(props, table);
this.click = this.click.bind(this);
this.getButtonConfig = this.getButtonConfig.bind(this);
}
click(args) {
this.table.componentRef.getTableRef().toggleFullscreen();
}
getButtonConfig() {
return {
name: this.name,
icon: () => {
return this.table.configStore.isFullscreen ? 'fullscreen_exit' : 'fullscreen';
},
label: () => {
return this.table.configStore.isFullscreen ? $t('logout') : $t('fullScreen');
},
click: this.click,
};
}
}

44
io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/buttons/InlineCellEdit.ts

@ -0,0 +1,44 @@
import { $t, Tools, NotifyManager } from '@/platform';
import { Button } from '../Button';
import { Constant, PropsType, TableType } from '../../index';
export class InlineCellEdit extends Button {
name = 'inlineCellEdit';
constructor(props: PropsType, table: TableType) {
super(props, table);
this.click = this.click.bind(this);
this.getButtonConfig = this.getButtonConfig.bind(this);
}
click(args) {
const selectedCell = args.grid.getSelectedCell();
if (args.grid.props.selectMode !== Constant.SELECT_MODE.CELL) {
console.warn('[w-grid]`selectMode` property is not `cell`, Cannot use cell editing function.');
return false;
} else if (Tools.isEmpty(selectedCell.colName)) {
NotifyManager.info('请选择要编辑的单元格');
return false;
} else if (!this.tools?.editFM.getEditorFieldByName(selectedCell.colName)) {
console.warn('[w-grid]The column selected is not configured with a component type for editing.');
return false;
}
this.table.store.inlineEditStatus = Constant.EDIT_STATUS.CELL;
}
getButtonConfig() {
return {
name: this.name,
icon: 'border_color',
labelI18nKey: 'action.edit',
label: $t('action.edit'),
enableIf: (args) => {
if (!Tools.isEmpty(args.grid.getSelectedCell()['colName'])) {
return true;
}
return false;
},
click: this.click,
};
}
}

37
io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/buttons/InlineRowEdit.ts

@ -0,0 +1,37 @@
import { $t } from '@/platform';
import { Button } from '../Button';
import { Constant, PropsType, TableType } from '../../index';
export class InlineRowEdit extends Button {
name = 'inlineRowEdit';
constructor(props: PropsType, table: TableType) {
super(props, table);
this.click = this.click.bind(this);
this.getButtonConfig = this.getButtonConfig.bind(this);
}
click(args) {
if (this.props?.editor?.form?.fields?.length > 0) {
this.table.store.inlineEditStatus = Constant.EDIT_STATUS.ROW;
} else {
console.warn('[w-grid]Not configured with a component type for editing.');
}
}
getButtonConfig() {
return {
name: this.name,
icon: 'edit_note',
labelI18nKey: 'action.edit',
label: $t('action.edit'),
enableIf: (args) => {
if (args.selected) {
return true;
}
return false;
},
click: this.click,
};
}
}

35
io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/buttons/InlineRowsEdit.ts

@ -0,0 +1,35 @@
import { $t } from '@/platform';
import { Button } from '../Button';
import { Constant, PropsType, TableType } from '../../index';
export class InlineRowsEdit extends Button {
name = 'inlineRowsEdit';
constructor(props: PropsType, table: TableType) {
super(props, table);
this.click = this.click.bind(this);
this.getButtonConfig = this.getButtonConfig.bind(this);
}
click(args) {
if (this.props.editor?.form?.fields?.length > 0) {
this.table.store.inlineEditStatus = Constant.EDIT_STATUS.ROWS;
// 清空勾选与选中
args.grid.clearSelected();
args.grid.clearTicked();
this.table.store.headerTicked = false;
} else {
console.warn('[w-grid]Not configured with a component type for editing.');
}
}
getButtonConfig() {
return {
name: this.name,
icon: 'app_registration',
labelI18nKey: 'action.edit',
label: $t('action.edit'),
click: this.click,
};
}
}

35
io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/buttons/MoreQuery.ts

@ -0,0 +1,35 @@
import { $t } from '@/platform';
import { Button } from '../Button';
import { Constant, PropsType, TableType } from '../../index';
export class MoreQuery extends Button {
name = 'moreQuery';
constructor(props: PropsType, table: TableType) {
super(props, table);
this.click = this.click.bind(this);
this.getButtonConfig = this.getButtonConfig.bind(this);
}
click(args) {
this.table.moreQueryStatus = !this.table.moreQueryStatus;
this.table.componentRef.getTopRef()?.handleQueryFormShowField();
}
getButtonConfig() {
return {
name: this.name,
icon: 'zoom_in',
labelI18nKey: 'action.moreQueryConditions',
label: $t('action.moreQueryConditions'),
enableIf: (args) => {
if (this.props.queryFormFields.length <= this.table.queryFormDisplayFields.length && !this.table.moreQueryStatus) {
return false;
} else {
return true;
}
},
click: this.click,
};
}
}

27
io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/buttons/Query.ts

@ -0,0 +1,27 @@
import { $t } from '@/platform';
import { PropsType, TableType } from '../../index';
import { Button } from '../Button';
export class Query extends Button {
name = 'query';
constructor(props: PropsType, table: TableType) {
super(props, table);
this.click = this.click.bind(this);
this.getButtonConfig = this.getButtonConfig.bind(this);
}
click(args) {
this.tools?.apiFM.operator.refreshGrid();
}
getButtonConfig() {
return {
name: this.name,
icon: 'search',
labelI18nKey: 'action.query',
label: $t('action.query'),
click: this.click,
};
}
}

27
io.sc.platform.core.frontend/src/platform/components/grid/ts/toolbar/buttons/Refresh.ts

@ -0,0 +1,27 @@
import { $t } from '@/platform';
import { PropsType, TableType } from '../../index';
import { Button } from '../Button';
export class Refresh extends Button {
name = 'refresh';
constructor(props: PropsType, table: TableType) {
super(props, table);
this.click = this.click.bind(this);
this.getButtonConfig = this.getButtonConfig.bind(this);
}
click(args) {
this.tools?.apiFM.operator.refreshGrid();
}
getButtonConfig() {
return {
name: this.name,
icon: 'loop',
labelI18nKey: 'action.refresh',
label: $t('action.refresh'),
click: this.click,
};
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save