提交 fd3bc382 编写于 作者: Mosher's avatar Mosher

update:新增树视图支持,调整树默认选中逻辑

上级 d1378061
......@@ -3,3 +3,4 @@
{{#*inline "GRID"}}{{>@macro/front-end/views/view-control/view-grid/view-control-grid.hbs}}{{/inline}}
{{#*inline "APPMENU"}}{{>@macro/front-end/views/view-control/view-menu/view-control-menu.hbs}}{{/inline}}
{{#*inline "PICKUPVIEWPANEL"}}{{>@macro/front-end/views/view-control/view-pickup-panel/view-pickup-panel.hbs}}{{/inline}}
{{#*inline "TREEVIEW"}}{{>@macro/front-end/views/view-control/view-tree/view-control-tree.hbs}}{{/inline}}
{{name}}: {
action:{
loadAction: '{{ctrl.getPSControlAction.psAppDEMethod.codeName}}',
removeAction: '{{ctrl.removePSControlAction.psAppDEMethod.codeName}}',
updateAction: '{{ctrl.updatePSControlAction.psAppDEMethod.codeName}}',
loadDraftAction: '{{ctrl.getDraftPSControlAction.psAppDEMethod.codeName}}',
createAction: '{{ctrl.createPSControlAction.psAppDEMethod.codeName}}',
fetchAction:'{{ctrl.fetchPSControlAction.psAppDEMethod.codeName}}'
}
}
\ No newline at end of file
<script setup lang="ts">
</script>
<template>
<AppViewBaseLayout>
<template v-slot:header-top>
<slot name="topMessage" />
</template>
<template v-slot:header-left>
<slot name="caption" />
</template>
<template v-slot:header-content>
<slot name="quickGroupSearch" />
<slot name="quickSearch" />
</template>
<template v-slot:header-right>
<slot name="toolbar" />
</template>
<template v-slot:header-bottom>
<slot name="quickSearchForm" />
<slot name="searchForm" />
<slot name="searchBar" />
</template>
<template v-slot:body-top>
<slot name="bodyMessage" />
</template>
<slot />
<template v-slot:footer-content>
<slot name="bottomMessage" />
</template>
</AppViewBaseLayout>
</template>
<style lang="scss">
</style>
\ No newline at end of file
......@@ -9,3 +9,4 @@ export * from './pickup-view'
export * from './mpickup-view'
export * from './pickup-grid-view'
export * from './tree-exp-view'
export * from './tree-view'
\ No newline at end of file
export * from './tree-view-props';
export * from './tree-view-state';
export * from './tree-view';
\ No newline at end of file
import { MDViewProps } from "@core";
/**
* @description 树视图props
* @export
* @interface TreeViewProps
* @extends {MDViewProps}
*/
export interface TreeViewProps extends MDViewProps {
}
\ No newline at end of file
import { MDViewState } from "@core";
/**
* @description 树视图状态
* @export
* @interface TreeViewState
* @extends {MDViewState}
*/
export interface TreeViewState extends MDViewState {
/**
* @description 支持快速搜索
* @type {boolean}
* @memberof TreeViewState
*/
enableQuickSearch: boolean;
/**
* @description 默认展开搜索表单
* @type {boolean}
* @memberof TreeViewState
*/
expandSearchForm: boolean;
}
\ No newline at end of file
import { IActionParam, IParam, MDView } from "@core";
import { TreeViewState } from "./tree-view-state";
/**
* @description 树视图
* @export
* @class TreeView
* @extends {MDView}
*/
export class TreeView extends MDView {
/**
* @description 视图状态数据
* @type {TreeViewState}
* @memberof TreeView
*/
public declare state: TreeViewState;
/**
* @description 当前视图部件
* @type {IParam}
* @memberof TreeView
*/
public declare tree: IParam;
/**
* @description 处理视图初始化
* @memberof TreeView
*/
public useViewInit(): void {
super.useViewInit();
this.tree = ref(null);
}
/**
* 处理部件事件
*
* @param {IActionParam} actionParam
* @memberof TreeView
*/
public onCtrlEvent(actionParam: IActionParam) {
const { tag, action, data } = actionParam;
const { xDataControlName } = this.state;
if (Object.is(tag, xDataControlName)) {
this.MDCtrlEvent(action, data);
return;
}
super.onCtrlEvent(actionParam);
}
/**
* 获取多数据部件
*
* @return {*} {*}
* @memberof TreeView
*/
public getMDCtrl(): any {
return unref(this.tree);
}
/**
* @description 安装视图所有功能模块的方法
* @return {*}
* @memberof TreeView
*/
public moduleInstall() {
const superParams = super.moduleInstall();
return {
...superParams,
tree: this.tree
};
}
}
\ No newline at end of file
import { MDControlState } from "@core";
import { IParam, MDControlState } from "@core";
/**
* @description 树部件通讯对象
......@@ -8,4 +8,38 @@ import { MDControlState } from "@core";
*/
export interface TreeControlState extends MDControlState {
/**
* @description 默认展开节点
* @type {string[]}
* @memberof TreeControlState
*/
expandedKeys: string[];
/**
* @description 回显选中节点集合
* @type {IParam[]}
* @memberof TreeControlState
*/
echoSelectedNodes: IParam[];
/**
* @description 枝干节点是否可用(具有数据能力,可抛出,树导航可用)
* @type {boolean}
* @memberof TreeControlState
*/
isBranchAvailable: boolean;
/**
* @description 选中节点
* @type {IParam[]}
* @memberof TreeControlState
*/
selectedNodes: IParam[];
/**
* @description 当前选中节点
* @type {IParam}
* @memberof TreeControlState
*/
currentSelectedNode: IParam;
}
\ No newline at end of file
import { deepCopy, deepObjectMerge, IActionParam, MDControl } from "@core";
import { deepCopy, deepObjectMerge, IActionParam, IParam, isEmpty, MDControl } from "@core";
import { TreeControlProps } from "./tree-control-prop";
import { TreeControlState } from "./tree-control-state";
......@@ -22,7 +22,7 @@ export class TreeControl extends MDControl {
*/
public setState(): void {
super.setState();
this.state.isBranchAvailable = toRef(this.props, 'isBranchAvailable') as any;
this.state.isBranchAvailable = toRef(this.props, 'isBranchAvailable') !== false;
}
/**
......@@ -36,15 +36,17 @@ export class TreeControl extends MDControl {
e.node.isCurrent = false;
return;
}
const { isBranchAvailable, currentselectedNode, multiple, selectedNodes, controlName } = this.state;
if (isBranchAvailable && e.node.leaf) {
if (currentselectedNode && Object.keys(currentselectedNode).length > 0) {
currentselectedNode.value.srfchecked = 0;
const { isBranchAvailable, currentSelectedNode, isMultiple } = this.state;
let { selectedNodes } = this.state;
if (isBranchAvailable && e.node.isLeaf) {
if (currentSelectedNode.value && Object.keys(currentSelectedNode.value).length > 0) {
currentSelectedNode.value.srfchecked = 0;
}
e.node.srfchecked = 1;
currentselectedNode.value = e.node;
if (!multiple) {
selectedNodes.push(currentselectedNode.value);
currentSelectedNode.value = e.node;
// 多选树树选中不识别,使用checkbox多选
if (!isMultiple) {
selectedNodes = [currentSelectedNode.value];
this.emit("ctrlEvent", { tag: this.props.name, action: 'selectionchange', data: deepCopy(selectedNodes) });
}
}
......@@ -76,7 +78,7 @@ export class TreeControl extends MDControl {
* @return {*} {Promise<any>}
* @memberof TreeControl
*/
protected async load(node: any, isFirst?: boolean): Promise<any> {}
protected async load(node: any, isFirst?: boolean): Promise<any> { }
/**
* @description 使用加载功能模块
......@@ -120,10 +122,9 @@ export class TreeControl extends MDControl {
return null;
}
const items = response.data;
// TODO 展开
// this.formatExpanded(items);
// this.formatAppendCaption(items);
const isRoot = Object.is(node?.level, 0);
this.formatExpanded(items);
this.formatAppendCaption(items);
const isRoot = !node || !node.parent;
if (isFirst) {
data.splice(0, data.length);
items.forEach((item: any) => {
......@@ -133,8 +134,7 @@ export class TreeControl extends MDControl {
node.dataRef.children = items;
}
const isSelectedAll = node?.checked;
// TODO 默认选中
// this.setDefaultSelection(items, isRoot, isSelectedAll);
this.setDefaultSelection(items, isRoot, isSelectedAll);
this.emit("ctrlEvent", { tag: this.props.name, action: "load", data: items });
} catch (error) {
console.error(error);
......@@ -162,6 +162,123 @@ export class TreeControl extends MDControl {
return load;
}
/**
* @description 设置默认展开节点
* @protected
* @param {IParam[]} items
* @memberof TreeControl
*/
protected formatExpanded(items: IParam[]) {
const { expandedKeys } = this.state;
items.forEach((item: any) => {
if (item.expanded) {
expandedKeys.push(item.id);
}
})
}
/**
* @description 设置附加标题栏
* @protected
* @param {IParam[]} items
* @memberof TreeControl
*/
protected formatAppendCaption(items: IParam[]) {
items.forEach(item => {
if (item.appendCaption && item.textFormat) {
item.text = item.textFormat + item.text;
}
});
}
protected setDefaultSelection(items: IParam[], isRoot: boolean = false, isSelectedAll: boolean = false): void {
if (items.length === 0) {
return;
}
const {
selectFirstDefault,
isMultiple,
viewParams,
currentSelectedNode,
isBranchAvailable
} = this.state;
let { selectedNodes, echoSelectedNodes } = this.state;
let defaultData: any;
// 导航视图中,有选中数据时选中该数据,无选中数据默认选中第一项
if (selectFirstDefault) {
// 单选
if (!isMultiple) {
let index: number = -1;
if (selectedNodes && selectedNodes.length > 0) {
// 单选时选中节点数组只有一项
const selectedNode: IParam = selectedNodes[0];
index = items.findIndex((item: IParam) => {
if (isEmpty(item.srfkey)) {
return selectedNode.id == item.id;
} else {
return selectedNode.srfkey == item.srfkey;
}
});
}
if (index === -1) {
if (isRoot) {
if (viewParams && viewParams.srfnavtag) {
const activate = viewParams.srfnavtag;
index = items.findIndex((item: any) => {
return item.id && item.id.split(';') && (item.id.split(';')[0] == activate);
});
if (index === -1) index = 0;
} else {
index = 0;
}
} else {
return;
}
}
defaultData = items[index];
// TODO 设置选中高亮
currentSelectedNode.value = deepCopy(defaultData);
if (isBranchAvailable || defaultData.isLeaf) {
selectedNodes = [currentSelectedNode.value];
this.emit("ctrlEvent", { tag: this.props.name, action: "selectionchange", data: selectedNodes });
}
}
}
// 回显已选数据
if (echoSelectedNodes && echoSelectedNodes.length > 0) {
const checkedNodes = items.filter((item: IParam) => {
return echoSelectedNodes.some((val: IParam) => {
if (Object.is(item.srfkey, val.srfkey) && Object.is(item.srfmajortext, val.srfmajortext)) {
val.used = true;
selectedNodes.push(val);
this.emit("ctrlEvent", { tag: this.props.name, action: "selectionchange", data: selectedNodes });
return true;
}
})
});
if (checkedNodes.length) {
// TODO 待确认响应式是否会消失
echoSelectedNodes = echoSelectedNodes.filter((item: any) => !item.used);
if (!isSelectedAll) {
if (isMultiple) {
selectedNodes = selectedNodes.concat(checkedNodes);
// TODO 设置选中树节点
} else {
// TODO 设置选中树节点高亮
currentSelectedNode.value = deepCopy(checkedNodes[0]);
selectedNodes = [currentSelectedNode.value];
}
}
}
}
// 父节点选中树,选中所有子节点
if (isSelectedAll) {
const leafNodes = items.filter((item: any) => item.isLeaf);
selectedNodes = selectedNodes.concat(leafNodes);
this.emit("ctrlEvent", { tag: this.props.name, action: 'selectionchange', data: selectedNodes });
}
}
/**
* @description 安装部件所有功能模块的方法
* @return {*}
......
......@@ -43,6 +43,13 @@ export interface TreeNodeVO {
*/
appDeDataSet?: IParam;
/**
* @description 附加标题
* @type {boolean}
* @memberof TreeNodeVO
*/
appendCaption?: boolean;
/**
* @description 附加父节点标识
* @type {boolean}
......
......@@ -189,7 +189,7 @@ export class TreeService<T extends ControlVOBase> extends ControlServiceBase<T>
enableckeck: node.enableCheck,
disabled: node.disableSelect,
expanded: node.expanded || filter.isAutoExpand,
leaf: !node.hasPSDETreeNodeRSs,
isLeaf: !node.hasPSDETreeNodeRSs,
selected: node.selected,
navfilter: node.navFilter,
navigateContext: node.navigateContext,
......@@ -247,7 +247,6 @@ export class TreeService<T extends ControlVOBase> extends ControlServiceBase<T>
}
try {
this.searchNodeData(node, context, searchFilter, filter).then((records: any) => {
console.log(222, "实体级", records);
if (records && records.length) {
records.forEach((entity: any) => {
let treeNode: any = {};
......@@ -324,18 +323,21 @@ export class TreeService<T extends ControlVOBase> extends ControlServiceBase<T>
} else {
Object.assign(treeNode, { expanded: filter.isAutoExpand });
}
if (node.appendCaption) {
Object.assign(treeNode, { appendCaption: true });
}
if (node.textFormat) {
Object.assign(treeNode, { textFormat: node.textFormat });
}
Object.assign(treeNode, { leaf: !node.hasPSDETreeNodeRSs });
Object.assign(treeNode, { isLeaf: !node.hasPSDETreeNodeRSs });
if (node.leafFlagPSAppDEField?.codeName) {
const leafFlag = entity[node.leafFlagPSAppDEField.codeName.toLowerCase()];
if (leafFlag != null && leafFlag != undefined) {
let strLeafFlag: string = leafFlag.toString().toLowerCase();
if (Object.is(strLeafFlag, '1') || Object.is(strLeafFlag, 'true')) {
Object.assign(treeNode, { leaf: true });
Object.assign(treeNode, { isLeaf: true });
} else {
Object.assign(treeNode, { leaf: false });
Object.assign(treeNode, { isLeaf: false });
}
}
}
......
......@@ -18,7 +18,7 @@ const routes = [
{{#if appEntityResource.appDataEntity.allPSAppViews}}
{{#each appEntityResource.appDataEntity.allPSAppViews as |appView|}}
{{#if (eq appView.getRefFlag true)}}
{{#if (or (eq appView.viewType 'DEEDITVIEW') (eq appView.viewType 'DEGRIDVIEW') (eq appView.viewType 'DETREEEXPVIEW'))}}
{{#if (or (eq appView.viewType 'DEEDITVIEW') (eq appView.viewType 'DEGRIDVIEW') (eq appView.viewType 'DETREEEXPVIEW') (eq appView.viewType 'DETREEVIEW'))}}
{
path: "{{appEntityResource.path}}/views/{{lowerCase appView.codeName}}",
meta: {
......
import {{page.codeName}} from "./{{spinalCase page.codeName}}.vue";
export default {{page.codeName}};
export const viewState = {
enableQuickSearch: {{#if page.enableQuickSearch}}{{page.enableQuickSearch}}{{else}}false{{/if}},
expandSearchForm: {{#if page.expandSearchForm}}{{page.expandSearchForm}}{{else}}false{{/if}},
{{> @macro/front-end/views/view-base-config.hbs}}
};
\ No newline at end of file
<script setup lang="ts">
import { Subject } from 'rxjs';
import { TreeView, IActionParam, IParam, IContext } from '@core';
import { viewState } from './{{spinalCase page.codeName}}-state';
{{#page.ctrls}}
{{#eq controlType "TREEVIEW"}}
import { {{codeName}}Tree } from '@widgets/{{spinalCase appEntity.codeName}}/{{spinalCase codeName}}-tree';
{{/eq}}
{{#if (and (eq controlType "SEARCHFORM") (eq name 'searchform'))}}
import { {{codeName}}SearchForm } from '@widgets/{{spinalCase appEntity.codeName}}/{{spinalCase codeName}}-search-form';
{{/if}}
{{#if (and (eq controlType "SEARCHFORM") (eq name 'quicksearchform'))}}
import { {{codeName}}QuickSearchForm } from '@widgets/{{spinalCase appEntity.codeName}}/{{spinalCase codeName}}-quick-search-form';
{{/if}}
{{#eq controlType "SEARCHBAR"}}
import { {{codeName}}SearchBar } from '@widgets/{{spinalCase appEntity.codeName}}/{{spinalCase codeName}}-searchBar';
{{/eq}}
{{/page.ctrls}}
// props声明和默认值处理
interface Props {
context?: IContext;
viewParams?: IParam;
openType?: "ROUTE" | "MODAL" | "EMBED";
viewSubject?: Subject<IActionParam>;
}
const props = withDefaults(defineProps<Props>(), {
openType:'ROUTE',
viewSubject: () => new Subject<IActionParam>()
})
// emit声明
interface ViewEmit {
(name: "onViewEvent", value: IActionParam): void;
}
const emit = defineEmits<ViewEmit>();
// 安装功能模块,提供状态和能力方法
const { state, tree, onCtrlEvent, onToolbarEvent, onQuickGroupEvent, onQuickSearchEvent } = new TreeView(viewState, props, emit).moduleInstall();
</script>
<template>
<AppTreeViewLayout :class="['app-tree-view', state.viewSysCss]">
<template v-slot:caption>
<AppIconText class="app-view__caption" size="large" :text="state.viewCaption" />
</template>
{{#if page.enableQuickGroup}}
<template v-slot:quickGroupSearch>
<AppQuickGroup v-if="state.enableQuickGroup" :quickGroupModel="state.quickGroupPSCodeList" @onQuickGroupEvent="onQuickGroupEvent" />
</template>
{{/if}}
{{#page.ctrls}}
{{#eq controlType "TOOLBAR"}}
<template v-slot:toolbar>
<AppToolbar
mode="button"
name="{{lowerCase codeName}}"
:actionModel="state.toolbar"
@onToolbarEvent="onToolbarEvent"/>
</template>
{{/eq}}
{{#if (and (eq controlType "SEARCHFORM") (eq name 'searchform'))}}
{{#if page.enableFilter}}
<template v-slot:quickSearch>
<div class='app-quick-search'>
<a-input v-if="state.enableQuickSearch" @pressEnter="onQuickSearchEvent($event)" allowClear/>
<a-popover trigger="click" :overlayStyle="{width: '50%'}">
<template #content>
<{{codeName}}SearchForm
v-if="state.expandSearchForm"
:context="state.context"
:viewParams="state.viewParams"
:controlAction="state.{{camelCase name}}.action"
:viewSubject="state.viewSubject"
@ctrlEvent="onCtrlEvent"
></{{codeName}}SearchForm>
</template>
<a-button><filter-outlined /></a-button>
</a-popover>
</div>
</template>
{{else}}
<template v-slot:searchForm>
<{{codeName}}SearchForm
v-if="state.expandSearchForm"
:context="state.context"
:viewParams="state.viewParams"
:controlAction="state.{{camelCase name}}.action"
:viewSubject="state.viewSubject"
@ctrlEvent="onCtrlEvent"
></{{codeName}}SearchForm>
</template>
{{/if}}
{{/if}}
{{#if (and (eq controlType "SEARCHFORM") (eq name 'quicksearchform'))}}
<template v-slot:quickSearchForm>
<{{codeName}}QuickSearchForm
:context="state.context"
:viewParams="state.viewParams"
:controlAction="state.{{camelCase name}}.action"
:viewSubject="state.viewSubject"
@ctrlEvent="onCtrlEvent"
></{{codeName}}QuickSearchForm>
</template>
{{/if}}
{{#eq controlType "SEARCHBAR"}}
<template v-slot:searchBar>
<{{codeName}}SearchBar
:controlAction="state.{{camelCase name}}.action"
:viewSubject="state.viewSubject"/>
</template>
{{/eq}}
{{#eq controlType "TREEVIEW"}}
<{{codeName}}Tree
ref="tree"
:name="state.xDataControlName"
:context="state.context"
:showBusyIndicator="true"
:viewParams="state.viewParams"
:controlAction="state.{{name}}.action"
:viewSubject="state.viewSubject"
@ctrlEvent="onCtrlEvent"
></{{codeName}}Tree>
{{/eq}}
{{/page.ctrls}}
</AppTreeViewLayout>
</template>
\ No newline at end of file
......@@ -4,6 +4,9 @@ export const ctrlState = {
counter: 0,
xDataControlName: '{{ctrl.xDataControlName}}',
selection: {},
showTitleBar: {{#if ctrl.showTitleBar}}true{{else}}false{{/if}},
title: '{{ctrl.title}}',
titleRes: '{{#if ctrl.titlePSLanguageRes}}{{ctrl.titlePSLanguageRes.lanResTag}}{{/if}}',
viewRefs: [
{{#each ctrl.psAppViewRefs as | viewRef |}}
{
......
......@@ -42,8 +42,13 @@ defineExpose({ state, name: '{{ctrl.name}}' });
</script>
<template>
<AppSplit class="app-tree-exp-bar{{#if ctrl.psSysCss}} {{ctrl.psSysCss.cssName}}{{/if}}" :value="0.2">
<div class="app-tree-exp-bar{{#if ctrl.psSysCss}} {{ctrl.psSysCss.cssName}}{{/if}}">
<AppSplit :value="0.2">
<template #left>
<div v-if="state.showTitleBar" class="tree-exp-bar-title">
<span>\{{ state.title }}</span>
</div>
<div class="tree-exp-bar-body">
{{#ctrl.ctrls}}
{{#eq controlType "TREEVIEW"}}
<{{codeName}}Tree
......@@ -51,11 +56,13 @@ defineExpose({ state, name: '{{ctrl.name}}' });
:context="state.context"
:viewParams="state.viewParams"
:viewSubject="state.viewSubject"
:selectFirstDefault="true"
:isBranchAvailable="true"
@ctrlEvent="onCtrlEvent"
></{{codeName}}Tree>
{{/eq}}
{{/ctrl.ctrls}}
</div>
</template>
<template #right>
{{#each ctrl.psAppViewRefs as | viewRef |}}
......@@ -70,4 +77,12 @@ defineExpose({ state, name: '{{ctrl.name}}' });
{{/each}}
</template>
</AppSplit>
</div>
</template>
<style lang="scss" scoped>
.app-tree-exp-bar {
width: 100%;
height: 100%;
}
</style>
\ No newline at end of file
......@@ -23,11 +23,14 @@ export class ControlVO extends ControlVOBase implements TreeControlVO {
keyField: '{{field.codeName}}',
{{/if}}
{{#if field.majorField}}
majorField: '{{field.majorField}}',
majorField: '{{field.codeName}}',
{{/if}}
{{/each}}
},
{{/if}}
{{#if treeNode.appendCaption}}
appendCaption: {{treeNode.appendCaption}},
{{/if}}
{{#if treeNode.psAppDEDataSet}}
appDeDataSet: { codeName: '{{treeNode.psAppDEDataSet.codeName}}' },
{{/if}}
......@@ -184,7 +187,9 @@ export const ctrlState = {
controlCodeName: '{{ctrl.codeName}}',
controlName: '{{ctrl.name}}',
controlService: new TreeService<ControlVO>(ControlVO, new {{pascalCase ctrl.psAppDataEntity.codeName}}Service() ),
currentselectedNode: {},
currentSelectedNode: {},
data: [],
echoSelectedNodes: [],
expandedKeys: [],
selectedNodes: []
};
\ No newline at end of file
Markdown 格式
0% or
您添加了 0 到此讨论。请谨慎行事。
先完成此消息的编辑!
想要评论请 注册