Browse Source
* 完善树表格拖拽,支持拖拽过程中停留在目标节点1.5秒将其展开;支持将数据拖入至无孩子节点的目标节点中。 * 新增树表格懒加载模式。 * 新增数据分组功能,支持根据分组字段提取独立行或合并分组。 * 新增高级查询功能,支持用户自定义组装查询条件。 * 新增自定义追加行功能。 * 内置按钮新增`fullScreen`,该按钮为全屏与退出动态变化。 * 用户配置中增加分组、分割线配置功能。 * 优化Form表单验证错误,先看是否有errorMessageI18nKey,有的情况下优先使用。 * 修复全屏时紧凑失效与底部边框不显示问题。 * 修复内置编辑窗口中的按钮提交报错后关闭窗口重新打开仍然处于loading状态的问题。 * beforeRemove与beforeRequestData事件增加同步操作,事件方法处理完成后再执行内部处理。main
123 changed files with 8744 additions and 5610 deletions
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
File diff suppressed because it is too large
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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; |
|||
} |
|||
} |
@ -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]; |
|||
} |
|||
} |
@ -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'; |
|||
} |
|||
} |
|||
} |
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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; |
|||
} |
@ -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'; |
|||
} |
@ -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, |
|||
}; |
|||
} |
|||
} |
@ -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, |
|||
}; |
|||
} |
|||
} |
@ -0,0 +1,63 @@ |
|||
/** |
|||
* 行数据字段名称定义 |
|||
*/ |
|||
export class FieldNames { |
|||
/** |
|||
* 前端主键 |
|||
*/ |
|||
public static ROW_KEY = '_rowKey_'; |
|||
|
|||
/** |
|||
* 排序号 |
|||
*/ |
|||
public static SORT_NO = '_sortNo_'; |
|||
|
|||
/** |
|||
* 树型表格行数据是否展开状态 |
|||
*/ |
|||
public static EXPAND = 'expand'; |
|||
|
|||
/** |
|||
* 树型表格行数据用于处理父节点勾选状态的属性(记录自身勾选数量,要么为0要么为1) |
|||
*/ |
|||
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, |
|||
}; |
|||
} |
|||
} |
@ -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, |
|||
}; |
|||
} |
|||
} |
@ -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, |
|||
}; |
|||
} |
|||
} |
@ -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, |
|||
}; |
|||
} |
|||
} |
@ -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, |
|||
}; |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
} |
@ -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 }); |
|||
} |
|||
} |
@ -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 }); |
|||
} |
|||
} |
@ -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 }); |
|||
} |
|||
} |
@ -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 }); |
|||
} |
|||
} |
@ -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 }); |
|||
} |
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
@ -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]; |
|||
} |
|||
} |
|||
} |
@ -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 }); |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
} |
@ -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], |
|||
}; |
|||
} |
|||
} |
@ -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(); |
|||
} |
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
}); |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
}); |
|||
} |
|||
} |
@ -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; |
|||
} |
|||
} |
|||
} |
@ -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; |
|||
}); |
|||
} |
|||
} |
@ -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 : ''; |
|||
} |
|||
} |
|||
} |
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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'); |
|||
} |
|||
} |
|||
} |
@ -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 || ''; |
|||
} |
|||
} |
@ -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.'); |
|||
} |
|||
} |
@ -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; |
|||
}); |
|||
} |
|||
}; |
@ -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 }; |
@ -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(); |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
} |
@ -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); |
|||
}, |
|||
}; |
|||
} |
|||
} |
@ -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); |
|||
}, |
|||
}; |
|||
} |
|||
} |
@ -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); |
|||
}, |
|||
}; |
|||
} |
|||
} |
@ -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, |
|||
}; |
|||
} |
|||
} |
@ -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, |
|||
}; |
|||
} |
|||
} |
@ -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); |
|||
}, |
|||
}; |
|||
} |
|||
} |
@ -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); |
|||
}, |
|||
}; |
|||
} |
|||
} |
@ -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, |
|||
}; |
|||
} |
|||
} |
@ -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}"`; |
|||
} |
|||
} |
@ -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, |
|||
}; |
|||
} |
|||
} |
@ -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, |
|||
}; |
|||
} |
|||
} |
@ -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, |
|||
}; |
|||
} |
|||
} |
@ -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, |
|||
}; |
|||
} |
|||
} |
@ -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, |
|||
}; |
|||
} |
|||
} |
@ -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, |
|||
}; |
|||
} |
|||
} |
@ -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…
Reference in new issue