Browse Source

修复部分问题

main
likunming 3 months ago
parent
commit
d1bbee4eb2
  1. 31
      io.sc.platform.core.frontend/src/platform/components/grid/Header.vue
  2. 22
      io.sc.platform.core.frontend/src/platform/components/grid/WGrid.vue
  3. 4
      io.sc.platform.core.frontend/src/platform/components/grid/css/sticky.css
  4. 2
      io.sc.platform.core.frontend/src/platform/components/grid/ts/GridTools.ts
  5. 4
      io.sc.platform.core.frontend/src/platform/components/grid/ts/computed/ComputedManager.ts
  6. 2
      io.sc.platform.core.frontend/src/platform/components/grid/ts/event/src/UpdateTickeds.ts
  7. 53
      io.sc.platform.core.frontend/src/platform/components/grid/ts/function/RowData.ts
  8. 8
      io.sc.platform.core.frontend/src/platform/components/grid/ts/types/table/StoreType.ts
  9. 148
      io.sc.platform.core.frontend/src/platform/components/toolbar/Buttons.vue
  10. 105
      io.sc.platform.core.frontend/src/platform/components/toolbar/ChildrenBtn.vue
  11. 78
      io.sc.platform.core.frontend/src/platform/components/toolbar/ComputedManager.ts
  12. 223
      io.sc.platform.core.frontend/src/platform/components/toolbar/Toolbar.ts
  13. 492
      io.sc.platform.core.frontend/src/platform/components/toolbar/WToolbar.vue

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

@ -29,7 +29,7 @@
:rowspan="c.rowspan" :rowspan="c.rowspan"
:colspan="c.colspan" :colspan="c.colspan"
:style="thStyleHandler(c, props.scope)" :style="thStyleHandler(c, props.scope)"
:class="c.classes ? c.classes + ' ' + isFirstColumn(c, cIndex) : ' ' + isFirstColumn(c, cIndex)" :class="c.classes ? c.classes + ' ' + isFirstColumn(c, cIndex) + ' ' + scrollClass(c.name) : ' ' + isFirstColumn(c, cIndex) + ' ' + scrollClass(c.name)"
:props="titleScopeHandler(c, props.scope)" :props="titleScopeHandler(c, props.scope)"
style="font-weight: bold" style="font-weight: bold"
:title="c.title" :title="c.title"
@ -60,7 +60,7 @@
<q-th <q-th
:props="scope" :props="scope"
:style="col.style + (col.name === '_sortNo_' ? 'padding: 0; min-width: 50px;width: 50px;max-width:50px' : '')" :style="col.style + (col.name === '_sortNo_' ? 'padding: 0; min-width: 50px;width: 50px;max-width:50px' : '')"
:class="col.classes" :class="col.classes + scrollClass(col.name)"
style="font-weight: bold" style="font-weight: bold"
:title="col.title" :title="col.title"
> >
@ -114,6 +114,20 @@ type MoreColumnTitleType = {
let moreColumnTitleMap = new Map<string, MoreColumnTitleType>(); let moreColumnTitleMap = new Map<string, MoreColumnTitleType>();
let allColumnMap = new Map(); let allColumnMap = new Map();
const scrollClass = (columnName: string) => {
if (tools.table.store.scrollLeft > 0) {
const lastColumn = tools.table.originalColumns[tools.props.stickyNum - 1];
const multiHeaderLastColumn = extractLastColumn(lastColumn);
if (multiHeaderLastColumn.length > 0 && multiHeaderLastColumn.includes(columnName)) {
//
return 'scroll-border-right';
} else if (lastColumn['name'] === columnName) {
return 'scroll-border-right';
}
}
return '';
};
const moreColumnTitleTableSelectionStyle = computed(() => { const moreColumnTitleTableSelectionStyle = computed(() => {
if (tools.table.configStore.stickyNum > 0) { if (tools.table.configStore.stickyNum > 0) {
if (tools.props.tree) { if (tools.props.tree) {
@ -242,6 +256,19 @@ const thStickyLastNameComputed = computed(() => {
return result; return result;
}); });
//
const extractLastColumn = (column: any) => {
const columns = <any>[];
if (column.columns) {
columns.push(column.columns[column.columns.length - 1]['name']);
const childrenResult = extractLastColumn(column.columns);
if (childrenResult.length > 0) {
columns.concat(childrenResult);
}
}
return columns;
};
const thStyleHandler = (c: any, scope: any) => { const thStyleHandler = (c: any, scope: any) => {
let style = ''; let style = '';
if (!Tools.isEmpty(c.style)) { if (!Tools.isEmpty(c.style)) {

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

@ -1,6 +1,6 @@
<template> <template>
<div v-show="tools.cm.showIf.value" class="w-grid" style="height: 100%"> <div v-show="tools.cm.showIf.value" class="w-grid" style="height: 100%">
<q-resize-observer debounce="500" @resize="tools.opFM.resize('table')" /> <q-resize-observer debounce="100" @resize="tools.opFM.resize('table')" />
<q-table <q-table
ref="tableRef" ref="tableRef"
v-model:pagination="tools.table.store.pagination" v-model:pagination="tools.table.store.pagination"
@ -27,7 +27,7 @@
> >
<!-- 查询表单与按钮工具栏 --> <!-- 查询表单与按钮工具栏 -->
<template #top="scope"> <template #top="scope">
<q-resize-observer debounce="500" @resize="tools.opFM.resize('top')" /> <q-resize-observer debounce="100" @resize="tools.opFM.resize('top')" />
<Top ref="topRef" :scope="scope"> </Top> <Top ref="topRef" :scope="scope"> </Top>
</template> </template>
@ -155,6 +155,19 @@ watchEffect(() => {
} }
}); });
const addScrollListener = () => {
const tableMiddle = getTableRef().$el.getElementsByClassName('q-table__middle')[0];
tableMiddle?.addEventListener('scroll', () => {
tools.table.store.scrollLeft = tableMiddle.scrollLeft;
tools.table.store.scrollTop = tableMiddle.scrollTop;
// TD
if (tools.table.store.scrollLeft > 0) {
//
}
});
};
// //
onMounted(() => { onMounted(() => {
tools.opFM.propsValidate(); tools.opFM.propsValidate();
@ -169,6 +182,11 @@ onMounted(() => {
} else { } else {
tools.opFM.resetStyleVariableValue(); tools.opFM.resetStyleVariableValue();
} }
//
if (tools.props.stickyNum > 0) {
addScrollListener();
}
}); });
const getTableRef = () => { const getTableRef = () => {

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

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

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

@ -161,6 +161,8 @@ export class GridTools {
loadingLabelI18nKey: 'tip.dataLoading', loadingLabelI18nKey: 'tip.dataLoading',
bodyCellHeight: bodyCellHeight, bodyCellHeight: bodyCellHeight,
resizeFlag: false, resizeFlag: false,
scrollLeft: 0,
scrollTop: 0,
location: { location: {
yLocation: 0, yLocation: 0,
noDataTrYLocation: 0, noDataTrYLocation: 0,

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

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

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

@ -18,7 +18,7 @@ export class UpdateTickeds extends Base {
const tickedField = this.props.tickedField; const tickedField = this.props.tickedField;
if (this.table?.store.inlineEditStatus === Constant.EDIT_STATUS.NONE) { if (this.table?.store.inlineEditStatus === Constant.EDIT_STATUS.NONE) {
this.table.rows.forEach((item) => { this.table.rows.forEach((item) => {
if (item[Constant.FIELD_NAMES.SELECTABLE]) { if (this.tools?.dataFM.getRowSelectable(item)) {
item[tickedField] = value; item[tickedField] = value;
item[selectedField] = value; item[selectedField] = value;
} }

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

@ -21,6 +21,7 @@ export class RowData extends Base {
this.checkLastRow = this.checkLastRow.bind(this); this.checkLastRow = this.checkLastRow.bind(this);
this.setExtraProperty = this.setExtraProperty.bind(this); this.setExtraProperty = this.setExtraProperty.bind(this);
this.arrayOrderBy = this.arrayOrderBy.bind(this); this.arrayOrderBy = this.arrayOrderBy.bind(this);
this.getRowSelectable = this.getRowSelectable.bind(this);
this.mergeSortFields = this.mergeSortFields.bind(this); this.mergeSortFields = this.mergeSortFields.bind(this);
this.mergeDefaultSortBy = this.mergeDefaultSortBy.bind(this); this.mergeDefaultSortBy = this.mergeDefaultSortBy.bind(this);
this.rowDataExtraPropertyHandle = this.rowDataExtraPropertyHandle.bind(this); this.rowDataExtraPropertyHandle = this.rowDataExtraPropertyHandle.bind(this);
@ -380,21 +381,63 @@ export class RowData extends Base {
} }
/** /**
* *
* @param rowData * @param rowData
* @returns * @returns
*/ */
private getSelectable(rowData: any): boolean { private getSelectable(rowData: any): boolean {
let selectable = true; let selectable: any = true;
if (this.props.selectableIf && typeof this.props.selectableIf === 'function') { const this_ = this;
const result = this.props.selectableIf({ grid: this.instance, data: rowData }); if (this_.props.selectMode === Constant.SELECT_MODE.NONE) {
if (!result) { selectable = false;
} else if (this_.props.selectMode === Constant.SELECT_MODE.ROW) {
if (this_.props.selectableIf && typeof this_.props.selectableIf === 'function') {
selectable = false; selectable = false;
const result = this_.props.selectableIf({ grid: this_.instance, data: rowData });
if (result === true) {
selectable = true;
}
}
} else if (this_.props.selectMode === Constant.SELECT_MODE.CELL) {
if (this_.props.selectableIf && typeof this_.props.selectableIf === 'function') {
selectable = {};
let cellSelectable = false;
for (let i = 0; i < this_.table.columns.length; i++) {
const colName = this_.table.columns[i]['name'];
if (colName !== Constant.FIELD_NAMES.SORT_NO) {
const result = this_.props.selectableIf({ grid: this_.instance, data: rowData, colName });
if (result === true) {
cellSelectable = true;
}
selectable[colName] = cellSelectable;
}
}
} }
} }
return selectable; return selectable;
} }
/**
* _selectable字段
* @param row
* @returns
*/
getRowSelectable(row) {
if (this.props.selectMode === Constant.SELECT_MODE.CELL && typeof row[Constant.FIELD_NAMES.SELECTABLE] === 'object') {
let selectable = true;
const keys = Object.keys(row[Constant.FIELD_NAMES.SELECTABLE]);
for (let i = 0; i < keys.length; i++) {
if (row[Constant.FIELD_NAMES.SELECTABLE][keys[i]] === false) {
selectable = false;
break;
}
}
return selectable;
} else {
return row[Constant.FIELD_NAMES.SELECTABLE];
}
}
/** /**
* ticked状态 * ticked状态
* @param rowData * @param rowData

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

@ -74,4 +74,12 @@ export type StoreType = {
* *
*/ */
pagination: PaginationType, pagination: PaginationType,
/**
*
*/
scrollLeft: number,
/**
*
*/
scrollTop: number,
} }

148
io.sc.platform.core.frontend/src/platform/components/toolbar/Buttons.vue

@ -0,0 +1,148 @@
<template>
<div
ref="divRef"
:class="'flex ' + (toolbar.props.align === 'left' ? 'justify-start' : toolbar.props.align === 'center' ? 'justify-center' : 'justify-end')"
:style="`column-gap:${toolbar.props.xGap}px`"
>
<template v-for="(btn, index) in baseButtons" :key="'button_' + index">
<!-- 分割符按钮 -->
<q-separator v-if="typeof btn.data === 'string' && btn.data === 'separator'" vertical class="class-action-item" />
<!-- 按钮组 -->
<q-btn-dropdown
v-else-if="Array.isArray(btn.data) && btn.data.length > 0"
unelevated
:dense="toolbar.props.dense"
outline
no-wrap
no-caps
v-bind="btn.data[0]"
:label="toolbar.props.dense ? '' : toolbar.getLabel(btn.data[0].label)"
:icon="toolbar.props.dense ? undefined : toolbar.getIcon(btn.data[0].icon)"
:split="btn.data[0].click ? true : false"
:disable="btn.data[0]?.enableIf ? !btn.data[0].enableIf(toolbar.cm.args.value) : false"
class="class-action-item"
content-class="w-toolbar-btn-dropdown"
@click="toolbar.buttonClick(btn.data[0])"
>
<template v-if="toolbar.props.dense" #label>
<div :style="btn.data[0].click ? 'padding: 0px 8px' : 'padding: 0px 5px 0px 8px'">
<q-icon v-if="btn.data[0].icon" :name="toolbar.getIcon(btn.data[0].icon)" size="xs"></q-icon>
<span style="padding-left: 3px">{{ toolbar.getLabel(btn.data[0].label) }}</span>
</div>
</template>
<q-list>
<template v-for="(childrenBtn, childrenIndex) in btn.data" :key="'button_c_' + childrenIndex">
<template v-if="childrenIndex === 0"></template>
<template v-else>
<q-separator v-if="typeof childrenBtn === 'string' && childrenBtn === 'separator'" />
<ChildrenBtn v-else-if="Array.isArray(childrenBtn) && childrenBtn.length > 0" :button="childrenBtn"></ChildrenBtn>
<q-item
v-else
v-close-popup
clickable
:disable="childrenBtn?.enableIf ? !childrenBtn.enableIf(toolbar.cm.args.value) : false"
@click="toolbar.buttonClick(childrenBtn)"
>
<q-item-section>
<q-item-label
><q-icon v-if="childrenBtn?.icon" :name="toolbar.getIcon(childrenBtn?.icon)" left size="20px"></q-icon>
{{ toolbar.getLabel(childrenBtn?.label) }}</q-item-label
>
</q-item-section>
</q-item>
</template>
</template>
</q-list>
</q-btn-dropdown>
<!-- 单个按钮 -->
<q-btn
v-else
:padding="toolbar.props.dense ? toolbar.padding : undefined"
:disable="btn?.data?.enableIf ? !btn.data.enableIf(toolbar.cm.args.value) : false"
no-wrap
no-caps
outline
v-bind="btn.data"
align="center"
:icon="toolbar.props.dense ? undefined : toolbar.getIcon(btn.data.icon)"
:label="toolbar.props.dense ? '' : toolbar.getLabel(btn.data.label)"
class="class-action-item"
@click="toolbar.buttonClick(btn.data)"
>
<div v-if="toolbar.props.dense">
<q-icon v-if="btn.data.icon" :name="toolbar.getIcon(btn.data.icon)" size="xs"></q-icon>
<span style="padding-left: 3px">{{ toolbar.getLabel(btn.data.label) }}</span>
</div>
</q-btn>
</template>
<!-- moreActions -->
<q-btn-dropdown
v-if="moreButtons.length > 0"
unelevated
outline
label=""
no-wrap
no-caps
:icon="undefined"
:dense="toolbar.props.dense"
class="class-more-action"
content-class="w-toolbar-btn-dropdown"
>
<template #label>
<div style="padding: 0px 5px 0px 8px">
<span>{{ $t('more') }}</span>
</div>
</template>
<q-list>
<template v-for="(childrenBtn, childrenIndex) in moreButtons" :key="'moreAction_' + childrenIndex">
<q-separator v-if="typeof childrenBtn.data === 'string' && childrenBtn.data === 'separator'" />
<ChildrenBtn v-else-if="Array.isArray(childrenBtn.data) && childrenBtn.data.length > 0" :button="childrenBtn.data"></ChildrenBtn>
<q-item
v-else
v-close-popup
clickable
:disable="childrenBtn?.data?.enableIf ? !childrenBtn.data.enableIf(toolbar.cm.args.value) : false"
@click="toolbar.buttonClick(childrenBtn.data)"
>
<q-item-section>
<q-item-label
><q-icon v-if="childrenBtn.data.icon" :name="toolbar.getIcon(childrenBtn.data.icon)" left size="20px"></q-icon>
{{ toolbar.getLabel(childrenBtn.data.label) }}</q-item-label
>
</q-item-section>
</q-item>
</template>
</q-list>
</q-btn-dropdown>
</div>
</template>
<script setup lang="ts">
import { inject, ref } from 'vue';
import { $t } from '@/platform';
import { Toolbar } from './Toolbar';
import ChildrenBtn from './ChildrenBtn.vue';
const divRef = ref();
const toolbar = <Toolbar>inject('toolbar');
const props = defineProps({
baseButtons: {
type: <any>Array,
default: () => {
return [];
},
},
moreButtons: {
type: <any>Array,
default: () => {
return [];
},
},
});
defineExpose({
divRef,
});
</script>

105
io.sc.platform.core.frontend/src/platform/components/toolbar/ChildrenBtn.vue

@ -1,21 +1,9 @@
<template> <template>
<q-item <q-item clickable :disable="button[0]?.enableIf ? !button[0].enableIf(toolbar.cm.args.value) : false">
clickable
:disable="
button[0]?.enableIf
? !button[0].enableIf({
selected: firstSelectedComputed,
selecteds: selectedComputed,
ticked: firstTickedComputed,
tickeds: tickedComputed,
grid: grid,
selectedColName: props.grid.getSelectedCell()['colName'],
})
: false
"
>
<q-item-section> <q-item-section>
<q-item-label><q-icon v-if="button[0].icon" :name="getIcon(button[0].icon)" left size="20px"></q-icon> {{ getLabel(button[0].label) }}</q-item-label> <q-item-label
><q-icon v-if="button[0].icon" :name="toolbar.getIcon(button[0].icon)" left size="20px"></q-icon> {{ toolbar.getLabel(button[0].label) }}</q-item-label
>
</q-item-section> </q-item-section>
<q-item-section side> <q-item-section side>
<q-icon name="keyboard_arrow_right" /> <q-icon name="keyboard_arrow_right" />
@ -26,45 +14,20 @@
<template v-for="(childrenBtn, index) in button" :key="index"> <template v-for="(childrenBtn, index) in button" :key="index">
<template v-if="index === 0 && !childrenBtn.click"></template> <template v-if="index === 0 && !childrenBtn.click"></template>
<q-separator v-else-if="typeof childrenBtn === 'string' && childrenBtn === 'separator'" /> <q-separator v-else-if="typeof childrenBtn === 'string' && childrenBtn === 'separator'" />
<ChildrenBtn <ChildrenBtn v-else-if="Array.isArray(childrenBtn) && childrenBtn.length > 0" :button="childrenBtn"></ChildrenBtn>
v-else-if="Array.isArray(childrenBtn) && childrenBtn.length > 0"
:dense="dense"
:button="childrenBtn"
:grid="grid"
:button-click="buttonClick"
:get-icon="getIcon"
:get-label="getLabel"
></ChildrenBtn>
<template v-else> <template v-else>
<q-item <q-item
v-close-popup v-close-popup
clickable clickable
:disable=" :disable="
childrenBtn?.enableIf childrenBtn?.enableIf ? !childrenBtn.enableIf(toolbar.cm.args.value) : button[0]?.enableIf ? !button[0].enableIf(toolbar.cm.args.value) : false
? !childrenBtn.enableIf({
selected: firstSelectedComputed,
selecteds: selectedComputed,
ticked: firstTickedComputed,
tickeds: tickedComputed,
grid: grid,
selectedColName: props.grid.getSelectedCell()['colName'],
})
: button[0]?.enableIf
? !button[0].enableIf({
selected: firstSelectedComputed,
selecteds: selectedComputed,
ticked: firstTickedComputed,
tickeds: tickedComputed,
grid: grid,
selectedColName: props.grid.getSelectedCell()['colName'],
})
: false
" "
@click="buttonClick(childrenBtn)" @click="toolbar.buttonClick(childrenBtn)"
> >
<q-item-section> <q-item-section>
<q-item-label <q-item-label
><q-icon v-if="childrenBtn.icon" :name="getIcon(childrenBtn.icon)" left size="20px"></q-icon> {{ getLabel(childrenBtn.label) }}</q-item-label ><q-icon v-if="childrenBtn.icon" :name="toolbar.getIcon(childrenBtn.icon)" left size="20px"></q-icon>
{{ toolbar.getLabel(childrenBtn.label) }}</q-item-label
> >
</q-item-section> </q-item-section>
</q-item> </q-item>
@ -75,56 +38,16 @@
</q-item> </q-item>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'; import { inject } from 'vue';
import { Toolbar } from './Toolbar';
const toolbar = <Toolbar>inject('toolbar');
const props = defineProps({ const props = defineProps({
button: { button: {
type: Array, type: <any>Array,
default: () => { default: () => {
return []; return [];
}, },
}, },
grid: {
type: Object,
default: () => {
return {};
},
},
buttonClick: {
type: Function,
default: () => {
return () => {};
},
},
getIcon: {
type: Function,
default: () => {
return () => {};
},
},
getLabel: {
type: Function,
default: () => {
return () => {};
},
},
dense: { type: Boolean, default: false },
});
const firstSelectedComputed = computed(() => {
if (Object.keys(props.grid).length > 0 && props.grid.getSelectedRows().length > 0) {
return props.grid.getSelectedRows()[0];
}
return undefined;
});
const selectedComputed = computed(() => {
return Object.keys(props.grid).length > 0 ? props.grid.getSelectedRows() : [];
});
const firstTickedComputed = computed(() => {
if (Object.keys(props.grid).length > 0 && props.grid.getTickedRows().length > 0) {
return props.grid.getTickedRows()[0];
}
return undefined;
});
const tickedComputed = computed(() => {
return Object.keys(props.grid).length > 0 ? props.grid.getTickedRows() : [];
}); });
</script> </script>

78
io.sc.platform.core.frontend/src/platform/components/toolbar/ComputedManager.ts

@ -0,0 +1,78 @@
import { computed } from 'vue';
export class ComputedManager {
props: any;
constructor(props: any) {
this.props = props;
}
/**
* click与enableIf等函数通用入参
*/
args = computed(() => {
return {
selected: this.firstSelected.value,
selecteds: this.selected.value,
ticked: this.firstTicked.value,
tickeds: this.ticked.value,
grid: this.props.grid,
selectedColName: this.selectedColName.value,
};
});
/**
* w-grid
*/
selected = computed(() => {
if (this.props.grid) {
return this.props.grid.getSelectedRows();
}
return [];
});
/**
* w-grid
*/
firstSelected = computed(() => {
const rows = this.selected.value;
if (rows.length > 0) {
return rows[0];
}
return undefined;
});
/**
* w-grid checkbox勾选的所有记录
*/
ticked = computed(() => {
if (this.props.grid) {
return this.props.grid.getTickedRows();
}
return [];
});
/**
* w-grid checkbox勾选的第一条数据
*/
firstTicked = computed(() => {
const rows = this.ticked.value;
if (rows.length > 0) {
return rows[0];
}
return undefined;
});
/**
* w-grid
*/
selectedColName = computed(() => {
if (this.props.grid) {
const cell = this.props.grid.getSelectedCell();
if (cell) {
return cell['colName'];
}
}
return undefined;
});
}

223
io.sc.platform.core.frontend/src/platform/components/toolbar/Toolbar.ts

@ -0,0 +1,223 @@
import { Tools } from '@/platform';
import { ModelRef, nextTick } from 'vue';
import { ComputedManager } from './ComputedManager';
export class Toolbar {
/**
* padding
*/
padding = '5px 12px 5px 8px';
/**
*
*/
moreButtonWidth = 100;
/**
*
*/
props: any;
/**
*
*/
modelValue: ModelRef<any>;
/**
*
*/
cm: ComputedManager;
/**
* name json value
*/
buttonsJson: any = {};
/**
*
*/
buttons: any[] = [];
constructor(props: any, modelValue: ModelRef<any>) {
// 初始化
this.props = props;
this.modelValue = modelValue;
this.cm = new ComputedManager(props);
this.extractButton(modelValue.value);
// 对外方法绑定 this,避免模板中直接配置方法时(非显式调用) this 失效
this.buttonClick = this.buttonClick.bind(this);
this.initButtons = this.initButtons.bind(this);
this.setButtonWidth = this.setButtonWidth.bind(this);
this.getIcon = this.getIcon.bind(this);
this.getLabel = this.getLabel.bind(this);
}
/**
*
* @param button
* @returns
*/
async buttonClick(button) {
// 准备按钮入参
const context = {};
const args = {
...this.cm.args.value,
context: context,
};
// 按钮enableIf校验
if (button.enableIf && !button.enableIf(args)) {
console.warn('[w-toolbar] The function `enableIf` returns false, causing the `click` not to trigger.' + ' button name is `' + button['name'] + '`');
return;
}
// beforeClick 执行
let beforeResult = true;
if (button.beforeClick) {
beforeResult = await button.beforeClick(args);
}
// click 执行
if (beforeResult && button.click) {
let callAfterEditorOpen = true;
const clickProps = { ...args };
if (button._click) {
if (button.overrideClick) {
callAfterEditorOpen = false;
}
clickProps['_click'] = (_args: any) => {
button._click(_args || args);
callAfterEditorOpen = true;
};
}
await button.click(clickProps);
// afterClick 执行
nextTick(async () => {
if (button.afterClick) {
await button.afterClick(args);
}
// afterEditorOpen 执行
if (button.afterEditorOpen && callAfterEditorOpen) {
button.afterEditorOpen(args);
}
});
}
}
/**
*
*/
initButtons() {
this.buttons = [];
for (let i = 0; i < this.modelValue.value.length; i++) {
const btn: any = this.modelValue.value[i];
if (i === 0 && typeof btn === 'string' && btn === 'separator') {
// 按钮数组第一个元素为分割符时直接跳过
continue;
}
if (typeof btn === 'string' && btn === 'separator' && this.buttons.length > 0 && this.buttons[this.buttons.length - 1].data === btn) {
// 连续的分割符直接跳过,避免出现重复的无意义分割符
continue;
}
if (Array.isArray(btn) && btn.length > 0) {
this.buttons.push({ data: this.handleChildrenAndSeparator(btn) });
} else if (typeof btn === 'string' && btn === 'separator') {
this.buttons.push({ data: btn });
} else {
this.buttons.push({ data: this.buttonsJson[btn.name] });
}
}
}
/**
*
* @param buttonsRef
*/
setButtonWidth(buttonsRef: any) {
const nodes = buttonsRef.getElementsByClassName('class-action-item');
this.moreButtonWidth = buttonsRef.getElementsByClassName('class-more-action')[0].clientWidth;
if (nodes) {
for (let i = 0; i < nodes.length; i++) {
if (this.buttons[i]) {
this.buttons[i].width = nodes[i].clientWidth;
}
}
}
}
/**
* icon
* @param icon
* @returns
*/
getIcon(icon) {
if (Tools.isUndefinedOrNull(icon)) {
return undefined;
} else if (typeof icon === 'function') {
return icon(this.cm.args);
} else {
return icon;
}
}
/**
* label
* @param label
* @returns
*/
getLabel(label) {
if (Tools.isUndefinedOrNull(label)) {
return '';
} else if (typeof label === 'function') {
return label(this.cm.args);
} else {
return label;
}
}
/**
* json中
* @param buttonArray
*/
private extractButton(buttonArray: any) {
for (const btn of buttonArray) {
if (Array.isArray(btn)) {
this.extractButton(btn);
} else if (typeof btn === 'object') {
this.buttonsJson[btn.name] = { ...btn };
if (this.props.noIcon) {
this.buttonsJson[btn.name].icon = undefined;
}
}
}
}
/**
*
* @param arr
* @returns
*/
private handleChildrenAndSeparator(arr) {
const tempArr = <any>[];
for (let i = 0; i < arr.length; i++) {
const btn = arr[i];
if (i === 0 && typeof btn === 'string' && btn === 'separator') {
// 按钮数组第一个元素为分割符时直接跳过
continue;
}
if (typeof btn === 'string' && btn === 'separator' && tempArr.length > 0 && tempArr[tempArr.length - 1] === btn) {
// 连续的分割符直接跳过,避免出现重复的无意义分割符
continue;
}
if (Array.isArray(btn) && btn.length > 0) {
const handleResult = this.handleChildrenAndSeparator(btn);
if (handleResult && handleResult.length > 0) {
tempArr.push(handleResult);
}
} else if (typeof btn === 'string' && btn === 'separator') {
tempArr.push(btn);
} else {
tempArr.push(this.buttonsJson[btn.name]);
}
}
return tempArr;
}
}

492
io.sc.platform.core.frontend/src/platform/components/toolbar/WToolbar.vue

@ -1,191 +1,25 @@
<template> <template>
<div class="w-toolbar"> <div class="w-toolbar">
<div <div ref="containerRef">
ref="containerRef" <Buttons :base-buttons="baseButtons" :more-buttons="moreButtons"></Buttons>
:class="'flex ' + (align === 'left' ? 'justify-start' : align === 'center' ? 'justify-center' : 'justify-end')" </div>
:style="`column-gap:${props.xGap}px`" <!-- 以下隐藏容器用来放置全部按钮获取按钮实际显示宽度时使用 -->
> <div ref="hiddenContainerRef" style="display: none">
<!-- buttons --> <Buttons ref="buttonsRef" :base-buttons="toolbar.buttons" :more-buttons="[{ data: 'separator' }]"></Buttons>
<template v-for="(btn, index) in baseActionsComputed" :key="'button_' + index">
<q-separator v-if="typeof btn.data === 'string' && btn.data === 'separator'" vertical class="class-action-item" />
<q-btn-dropdown
v-else-if="Array.isArray(btn.data) && btn.data.length > 0"
unelevated
:dense="dense"
outline
no-wrap
no-caps
v-bind="btn.data[0]"
:label="dense ? '' : getLabel(btn.data[0].label)"
:icon="dense ? undefined : getIcon(btn.data[0].icon)"
:split="btn.data[0].click ? true : false"
:disable="
btn.data[0]?.enableIf
? !btn.data[0].enableIf({
selected: firstSelectedComputed,
selecteds: selectedComputed,
ticked: firstTickedComputed,
tickeds: tickedComputed,
selectedColName: props.grid.getSelectedCell()['colName'],
grid: grid,
})
: false
"
class="class-action-item"
content-class="w-toolbar-btn-dropdown"
@click="buttonClick(btn.data[0])"
>
<template v-if="dense" #label>
<div :style="btn.data[0].click ? 'padding: 0px 8px' : 'padding: 0px 5px 0px 8px'">
<q-icon v-if="btn.data[0].icon" :name="getIcon(btn.data[0].icon)" size="xs"></q-icon>
<span style="padding-left: 3px">{{ getLabel(btn.data[0].label) }}</span>
</div>
</template>
<q-list>
<template v-for="(childrenBtn, childrenIndex) in btn.data" :key="'button_c_' + childrenIndex">
<template v-if="childrenIndex === 0"></template>
<template v-else>
<q-separator v-if="typeof childrenBtn === 'string' && childrenBtn === 'separator'" />
<ChildrenBtn
v-else-if="Array.isArray(childrenBtn) && childrenBtn.length > 0"
:button="childrenBtn"
:grid="grid"
:button-click="buttonClick"
:get-icon="getIcon"
:get-label="getLabel"
:dense="dense"
></ChildrenBtn>
<q-item
v-else
v-close-popup
clickable
:disable="
childrenBtn?.enableIf
? !childrenBtn.enableIf({
selected: firstSelectedComputed,
selecteds: selectedComputed,
ticked: firstTickedComputed,
tickeds: tickedComputed,
grid: grid,
selectedColName: props.grid.getSelectedCell()['colName'],
})
: false
"
@click="buttonClick(childrenBtn)"
>
<q-item-section>
<q-item-label
><q-icon v-if="childrenBtn?.icon" :name="getIcon(childrenBtn?.icon)" left size="20px"></q-icon>
{{ getLabel(childrenBtn?.label) }}</q-item-label
>
</q-item-section>
</q-item>
</template>
</template>
</q-list>
</q-btn-dropdown>
<q-btn
v-else
:padding="dense ? padding : undefined"
:disable="
btn?.data?.enableIf
? !btn.data.enableIf({
selected: firstSelectedComputed,
selecteds: selectedComputed,
ticked: firstTickedComputed,
tickeds: tickedComputed,
grid: grid,
selectedColName: props.grid.getSelectedCell()['colName'],
})
: false
"
no-wrap
no-caps
outline
v-bind="btn.data"
align="center"
:icon="dense ? undefined : getIcon(btn.data.icon)"
:label="dense ? '' : getLabel(btn.data.label)"
class="class-action-item"
@click="buttonClick(btn.data)"
>
<div v-if="dense">
<q-icon v-if="btn.data.icon" :name="getIcon(btn.data.icon)" size="xs"></q-icon>
<span style="padding-left: 3px">{{ getLabel(btn.data.label) }}</span>
</div>
</q-btn>
</template>
<!-- moreActions -->
<q-btn-dropdown
v-if="moreActionsComputed && moreActionsComputed.length > 0"
unelevated
outline
label=""
no-wrap
no-caps
:icon="undefined"
:dense="dense"
class="class-action-item"
content-class="w-toolbar-btn-dropdown"
>
<template #label>
<div style="padding: 0px 5px 0px 8px">
<span>{{ $t('more') }}</span>
</div>
</template>
<q-list>
<template v-for="(childrenBtn, childrenIndex) in moreActionsComputed" :key="'moreAction_' + childrenIndex">
<q-separator v-if="typeof childrenBtn.data === 'string' && childrenBtn.data === 'separator'" />
<ChildrenBtn
v-else-if="Array.isArray(childrenBtn.data) && childrenBtn.data.length > 0"
:button="childrenBtn.data"
:grid="grid"
:button-click="buttonClick"
:get-icon="getIcon"
:get-label="getLabel"
:dense="dense"
></ChildrenBtn>
<q-item
v-else
v-close-popup
clickable
:disable="
childrenBtn?.data?.enableIf
? !childrenBtn.data.enableIf({
selected: firstSelectedComputed,
selecteds: selectedComputed,
ticked: firstTickedComputed,
tickeds: tickedComputed,
grid: grid,
selectedColName: props.grid.getSelectedCell()['colName'],
})
: false
"
@click="buttonClick(childrenBtn.data)"
>
<q-item-section>
<q-item-label
><q-icon v-if="childrenBtn.data.icon" :name="getIcon(childrenBtn.data.icon)" left size="20px"></q-icon>
{{ getLabel(childrenBtn.data.label) }}</q-item-label
>
</q-item-section>
</q-item>
</template>
</q-list>
</q-btn-dropdown>
</div> </div>
<q-resize-observer @resize="onResize" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, computed, nextTick, onUpdated, watch } from 'vue'; import { onMounted, provide, ref, nextTick, watch } from 'vue';
import { eventBus } from '@/platform'; import Buttons from './Buttons.vue';
import { Tools } from '@/platform/utils'; import { Toolbar } from './Toolbar';
import ChildrenBtn from './ChildrenBtn.vue';
const padding = '5px 12px 5px 8px';
const modelValue = defineModel({ type: Array, default: [] }); const modelValue = defineModel({ type: Array, default: [] });
const containerRef = ref();
const hiddenContainerRef = ref();
const buttonsRef = ref();
const baseButtons = ref(<any>[]);
const moreButtons = ref(<any>[]);
const props = defineProps({ const props = defineProps({
xGap: { type: Number, default: 6 }, // x xGap: { type: Number, default: 6 }, // x
noIcon: { type: Boolean, default: false }, // icon noIcon: { type: Boolean, default: false }, // icon
@ -198,271 +32,77 @@ const props = defineProps({
}, },
}, },
}); });
const containerRef = ref(); const toolbar = new Toolbar(props, modelValue);
const localFlag = ref(false); provide('toolbar', toolbar);
watch( watch(
() => modelValue.value, () => modelValue.value,
(newVal, oldVal) => { (newVal, oldVal) => {
// handleButtons(); //
// if (containerRef.value) { buildToolbar();
// isActionWidthInitializedRef.value = false;
// onResize(containerRef.value.clientWidth);
// }
}, },
); );
// name JSON /**
const buttonsJson = {}; * 计算按钮实际显示需要的宽度
const extractButton = (btns: any) => { */
for (const btn of btns) { const calcButtonWidth = () => {
if (Array.isArray(btn)) { //
extractButton(btn); hiddenContainerRef.value.style.display = 'block';
} else if (typeof btn === 'object') { nextTick(() => {
buttonsJson[btn.name] = { ...btn }; toolbar.setButtonWidth(buttonsRef.value.divRef);
if (props.noIcon) { //
buttonsJson[btn.name].icon = undefined; hiddenContainerRef.value.style.display = 'none';
} buildButtons();
} });
}
};
extractButton(modelValue.value);
const buttons_ = <any>[];
const handleChildrenSeparator = (arr) => {
const tempArr = <any>[];
for (let i = 0; i < arr.length; i++) {
const btn = arr[i];
if (i === 0 && typeof btn === 'string' && btn === 'separator') {
//
continue;
}
if (typeof btn === 'string' && btn === 'separator' && tempArr.length > 0 && tempArr[tempArr.length - 1] === btn) {
//
continue;
}
if (Array.isArray(btn) && btn.length > 0) {
const handleResult = handleChildrenSeparator(btn);
if (handleResult && handleResult.length > 0) {
tempArr.push(handleResult);
}
} else if (typeof btn === 'string' && btn === 'separator') {
tempArr.push(btn);
} else {
tempArr.push(buttonsJson[btn.name]);
}
}
return tempArr;
};
const handleButtons = () => {
buttons_.splice(0, buttons_.length);
for (let i = 0; i < modelValue.value.length; i++) {
const btn: any = modelValue.value[i];
if (i === 0 && typeof btn === 'string' && btn === 'separator') {
//
continue;
}
if (typeof btn === 'string' && btn === 'separator' && buttons_.length > 0 && buttons_[buttons_.length - 1].data === btn) {
//
continue;
}
if (Array.isArray(btn) && btn.length > 0) {
buttons_.push({ data: handleChildrenSeparator(btn) });
} else if (typeof btn === 'string' && btn === 'separator') {
buttons_.push({ data: btn });
} else {
buttons_.push({ data: buttonsJson[btn.name] });
}
}
};
handleButtons();
const baseActions = ref(buttons_);
const moreActions: any = ref([]);
const isActionWidthInitializedRef = ref(false);
const moreActionWidth = 100;
const onResize = (size) => {
if (Tools.isUndefinedOrNull(containerRef.value)) {
return;
}
if (!isActionWidthInitializedRef.value) {
const nodes = containerRef.value.getElementsByClassName('class-action-item');
for (let i = 0; i < buttons_.length; i++) {
if (nodes[i]) {
buttons_[i].width = nodes[i].clientWidth;
}
}
isActionWidthInitializedRef.value = true;
}
reset(size.width);
}; };
const reset = (currentWidth) => { //
const _baseActions = <any>[]; const buildButtons = () => {
const _moreActions = <any>[]; baseButtons.value = [];
const length = buttons_.length; moreButtons.value = [];
let availableWidth = currentWidth; //
let availableWidth = containerRef.value.clientWidth;
//
const buttonsWidth = toolbar.buttons.reduce((width, button) => width + button.width + props.xGap, 0);
if (buttonsWidth > availableWidth) {
// >
availableWidth -= toolbar.moreButtonWidth;
availableWidth -= props.xGap;
}
//
let width = 0; let width = 0;
let index = 0; //
for (; index < length; index++) { //
if (width + buttons_[index].width > availableWidth) { let moreFlag = false;
availableWidth -= moreActionWidth; for (let i = 0; i < toolbar.buttons.length; i++) {
while (width > availableWidth) { const button = toolbar.buttons[i];
index--; // > +
if (index >= 0) { if (availableWidth > width + button.width + props.xGap && !moreFlag) {
width -= buttons_[index].width; width += button.width + props.xGap;
_baseActions.pop(); baseButtons.value.push(button);
} else {
break;
}
}
break;
} else { } else {
_baseActions.push(buttons_[index]); moreFlag = true;
width += buttons_[index].width; moreButtons.value.push(button);
width += props.xGap;
} }
} }
for (; index < length; index++) {
_moreActions.push(buttons_[index]);
}
baseActions.value = _baseActions;
moreActions.value = _moreActions;
localFlag.value = !localFlag.value;
}; };
const baseActionsComputed = computed(() => { // toolbar
if (localFlag.value) { const buildToolbar = () => {
return baseActions.value; // modelValue
} toolbar.initButtons();
return baseActions.value; //
}); calcButtonWidth();
const moreActionsComputed = computed(() => {
if (localFlag.value) {
return moreActions.value;
}
return moreActions.value;
});
eventBus.on('onLocaleChanged', () => {
nextTick(() => {
extractButton(modelValue.value);
handleButtons();
baseActions.value = buttons_;
isActionWidthInitializedRef.value = false;
if (!Tools.isUndefinedOrNull(containerRef.value)) {
reset(containerRef.value.offsetWidth);
}
});
});
const loadingComputed = computed(() => {
return function (btn) {
if (btn.loadingIf && typeof btn.loadingIf === 'function' && btn.loadingIf(selectedComputed.value, tickedComputed.value, props.grid)) {
return true;
} else {
return false;
}
};
});
const getIcon = (icon) => {
if (Tools.isUndefinedOrNull(icon)) {
return undefined;
} else if (typeof icon === 'function') {
return icon({
selected: firstSelectedComputed.value,
selecteds: selectedComputed.value,
ticked: firstTickedComputed.value,
tickeds: tickedComputed.value,
grid: props.grid,
selectedColName: props.grid.getSelectedCell()['colName'],
});
} else {
return icon;
}
}; };
const getLabel = (label) => {
if (Tools.isUndefinedOrNull(label)) {
return '';
} else if (typeof label === 'function') {
return label({
selected: firstSelectedComputed.value,
selecteds: selectedComputed.value,
ticked: firstTickedComputed.value,
tickeds: tickedComputed.value,
grid: props.grid,
selectedColName: props.grid.getSelectedCell()['colName'],
});
} else {
return label;
}
};
const firstSelectedComputed = computed(() => {
if (Object.keys(props.grid).length > 0 && props.grid.getSelectedRows().length > 0) {
return props.grid.getSelectedRows()[0];
}
return undefined;
});
const selectedComputed = computed(() => {
return Object.keys(props.grid).length > 0 ? props.grid.getSelectedRows() : [];
});
const firstTickedComputed = computed(() => {
if (Object.keys(props.grid).length > 0 && props.grid.getTickedRows().length > 0) {
return props.grid.getTickedRows()[0];
}
return undefined;
});
const tickedComputed = computed(() => {
return Object.keys(props.grid).length > 0 ? props.grid.getTickedRows() : [];
});
const buttonClick = async (button) => { onMounted(() => {
const context = {}; buildToolbar();
const args = { });
selected: firstSelectedComputed.value,
selecteds: selectedComputed.value,
ticked: firstTickedComputed.value,
tickeds: tickedComputed.value,
grid: props.grid,
context: context,
selectedColName: props.grid.getSelectedCell()['colName'],
};
if (button.enableIf && !button.enableIf(args)) {
console.warn('[w-toolbar] The function `enableIf` returns false, causing the `click` not to trigger.' + ' button name is `' + button['name'] + '`');
return;
}
let beforeResult = true;
if (button.beforeClick) {
beforeResult = await button.beforeClick(args);
}
if (beforeResult && button.click) {
let callAfterEditorOpen = true;
const clickProps = { ...args };
if (button._click) {
if (button.overrideClick) {
callAfterEditorOpen = false;
}
clickProps['_click'] = (_args: any) => {
button._click(_args || args);
callAfterEditorOpen = true;
};
}
await button.click(clickProps);
nextTick(async () => {
if (button.afterClick) {
await button.afterClick(args);
}
if (button.afterEditorOpen && callAfterEditorOpen) {
button.afterEditorOpen(args);
}
});
}
};
defineExpose({ defineExpose({
buttonClick, buttonClick: toolbar.buttonClick,
buildToolbar,
}); });
</script> </script>

Loading…
Cancel
Save