20260915221013
This commit is contained in:
1 parent
1f56cc3efb
commit
dce29f250e
54 files changed
+3732
-574
No files matched your search
@@ -5,11 +5,18 @@
|
||||
* 渲染、交互、拖拽、右键菜单、键盘导航、无障碍全部由 ui/Tree 承担;本组件只做 FMS 业务约定适配:
|
||||
* 1. 默认字段名 b_id / b_name / children,调用方不必重复书写 fieldNames;
|
||||
* 2. 节点标题按 b_i18n 翻译(translate(b_i18n, b_name || b_code || b_id)),关闭 translateTitle 则直接取原值;
|
||||
* 3. 可选「全部分类」虚拟根节点(showRoot),默认展开、可折叠,调用方无需感知 rootKey。
|
||||
* 3. 可选「全部分类」虚拟根节点(showRoot),默认展开、可折叠;调用方只以「选中键为空」
|
||||
* 表达「选中了根」,不需要知道 rootKey,详见下方「虚拟根的选中约定」;
|
||||
* 4. 加载态(loading)由本组件统一渲染遮罩,调用方不必各自写 Spin 与定位样式。
|
||||
*
|
||||
* 除下表声明的 props 外,其余 props 与事件(draggable / filter / disabled / contextMenu /
|
||||
* selectedKeys / checkedKeys / lazy / v-model:selected-keys …)经 $attrs 原样透传给 ui/Tree,
|
||||
* 事件名与 ui/Tree 保持一致。
|
||||
* 事件名与 ui/Tree 保持一致;其中 select 事件额外做了一层归一化,见下。
|
||||
*
|
||||
* 虚拟根的选中约定(showRoot 时):
|
||||
* - 传入:selectedKeys 为 [] / 空值时,本组件自动把虚拟根当作选中项渲染(高亮「全部分类」);
|
||||
* - 传出:用户点中虚拟根时,select 事件回传的 keys 是 [](不是 [rootKey])。
|
||||
* 于是调用方只需用「falsy 的选中键」表达「全部」这一个概念,无需 import rootKey、也无需再判 isFmsRoot。
|
||||
*
|
||||
* 注意:本组件始终向 ui/Tree 提供 #title 插槽(默认渲染翻译后的标题),因此 ui/Tree 内置的
|
||||
* filter 关键词高亮不再生效(过滤本身照常生效);需要高亮时在 #title 插槽内自行实现。
|
||||
@@ -17,6 +24,7 @@
|
||||
* 用法:
|
||||
* <FmsTree
|
||||
* :tree-data="moduleTree"
|
||||
* :loading="categoryLoading"
|
||||
* :expanded-keys="expandedKeys"
|
||||
* :selected-keys="selectedId ? [selectedId] : []"
|
||||
* :context-menu="buildContextMenu"
|
||||
@@ -29,12 +37,18 @@
|
||||
* </template>
|
||||
* </FmsTree>
|
||||
*/
|
||||
import { computed, ref } from 'vue'
|
||||
import { Tree } from '@/components/ui'
|
||||
import { computed, ref, useAttrs } from 'vue'
|
||||
import { Spin, Tree } from '@/components/ui'
|
||||
import { translate } from '@/stores/i18n'
|
||||
|
||||
defineOptions({ inheritAttrs: false })
|
||||
|
||||
/** 未声明的 ui/Tree props(含 selectedKeys)经 $attrs 透传,归一化时需读取原值 */
|
||||
const attrs = useAttrs()
|
||||
|
||||
/** 虚拟根节点 key:纯内部实现细节,调用方用「空选中键」表达选中根,不需要也不应引用它 */
|
||||
const ALL_CATEGORY_KEY = '__fms_tree_root__'
|
||||
|
||||
const props = defineProps({
|
||||
/** 业务树数据(b_id / b_name / children) */
|
||||
treeData: { type: Array, default: () => [] },
|
||||
@@ -45,9 +59,11 @@ const props = defineProps({
|
||||
/** 是否注入「全部分类」虚拟根节点 */
|
||||
showRoot: { type: Boolean, default: false },
|
||||
/** 虚拟根节点 key */
|
||||
rootKey: { type: [String, Number], default: '__fms_tree_root__' },
|
||||
rootKey: { type: [String, Number], default: ALL_CATEGORY_KEY },
|
||||
/** 虚拟根节点标题,空则取多语言键 common.allCategories */
|
||||
rootTitle: { type: String, default: '' },
|
||||
/** 加载态:树区域盖一层居中 Spin 遮罩(懒加载子节点的局部 loading 仍由 ui/Tree 负责) */
|
||||
loading: { type: Boolean, default: false },
|
||||
/**
|
||||
* 展开键。undefined 表示非受控:无虚拟根时原样交给 ui/Tree(default-expand-all 等初始展开配置照常生效),
|
||||
* 有虚拟根时由本组件用内部状态维护,并照常抛出 update:expandedKeys。
|
||||
@@ -55,7 +71,7 @@ const props = defineProps({
|
||||
expandedKeys: { type: Array, default: undefined },
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:expandedKeys'])
|
||||
const emit = defineEmits(['update:expandedKeys', 'select'])
|
||||
|
||||
/** showRoot 且调用方未受控时,本组件用内部状态持有展开键(始终不含 rootKey) */
|
||||
const internalExpandedKeys = ref([])
|
||||
@@ -102,6 +118,32 @@ const rootCollapsed = ref(false)
|
||||
|
||||
const hasRootKey = (keys) => keys.some((key) => String(key) === String(props.rootKey))
|
||||
|
||||
/**
|
||||
* 选中键归一化(仅 showRoot 时生效):
|
||||
* 传入 [] / 空值 → 补成 [rootKey],让虚拟根呈现选中态,调用方用「空」表达「选中全部」;
|
||||
* 传入非空数组 → 原样透传,业务节点的选中完全由调用方决定。
|
||||
* 只补虚拟根这一个 key,不会顺手选中任何业务节点。
|
||||
*
|
||||
* 注意 selectedKeys 与其他 ui/Tree props 一样走 $attrs(本组件未声明它),
|
||||
* 因此这里必须从 attrs 取值;若从 props 取会永远拿到 undefined,
|
||||
* 从而把调用方传的真实选中项覆盖成虚拟根。
|
||||
*/
|
||||
const effectiveSelectedKeys = computed(() => {
|
||||
const keys = attrs.selectedKeys
|
||||
if (!props.showRoot) return keys
|
||||
if (keys && keys.length) return keys
|
||||
return [props.rootKey]
|
||||
})
|
||||
|
||||
/**
|
||||
* select 事件归一化:点中虚拟根时回传 [] 而非 [rootKey]。
|
||||
* 与 effectiveSelectedKeys 是一对镜像处理,保证调用方永远看不到 rootKey。
|
||||
*/
|
||||
function onSelect(keys, info) {
|
||||
const isRoot = info?.node?.isFmsRoot || hasRootKey(keys)
|
||||
emit('select', isRoot ? [] : keys, info)
|
||||
}
|
||||
|
||||
/**
|
||||
* 虚拟根默认展开(调用方的展开键里不需要出现 rootKey);
|
||||
* 用户收起根之后不再强制补回,避免箭头点不动。
|
||||
@@ -128,21 +170,46 @@ function onExpandedKeysChange(keys) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Tree
|
||||
v-bind="$attrs"
|
||||
:tree-data="effectiveTreeData"
|
||||
:field-names="fn"
|
||||
:expanded-keys="effectiveExpandedKeys"
|
||||
@update:expanded-keys="onExpandedKeysChange"
|
||||
>
|
||||
<!-- 标题:默认渲染翻译后的文本;调用方可用 #title 覆盖,并从插槽参数取翻译结果 -->
|
||||
<template #title="scope">
|
||||
<slot name="title" :node="scope.node" :title="resolveNodeTitle(scope.node)">
|
||||
{{ resolveNodeTitle(scope.node) }}
|
||||
<div class="fms-tree">
|
||||
<Tree
|
||||
v-bind="$attrs"
|
||||
:tree-data="effectiveTreeData"
|
||||
:field-names="fn"
|
||||
:expanded-keys="effectiveExpandedKeys"
|
||||
:selected-keys="effectiveSelectedKeys"
|
||||
@update:expanded-keys="onExpandedKeysChange"
|
||||
@select="onSelect"
|
||||
>
|
||||
<!-- 标题:默认渲染翻译后的文本;调用方可用 #title 覆盖,并从插槽参数取翻译结果 -->
|
||||
<template #title="scope">
|
||||
<slot name="title" :node="scope.node" :title="resolveNodeTitle(scope.node)">
|
||||
{{ resolveNodeTitle(scope.node) }}
|
||||
</slot>
|
||||
</template>
|
||||
<template v-if="$slots.empty" #empty>
|
||||
<slot name="empty" />
|
||||
</template>
|
||||
</Tree>
|
||||
<!-- 加载遮罩盖住树区域;#loading 插槽可覆盖默认的居中 Spin -->
|
||||
<div v-if="loading" class="fms-tree__loading">
|
||||
<slot name="loading">
|
||||
<Spin />
|
||||
</slot>
|
||||
</template>
|
||||
<template v-if="$slots.empty" #empty>
|
||||
<slot name="empty" />
|
||||
</template>
|
||||
</Tree>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fms-tree {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.fms-tree__loading {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: rgb(255 255 255 / 60%);
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user