20260914172820
This commit is contained in:
1 parent
c31329d387
commit
549c8bd2eb
38 files changed
+3430
-502
No files matched your search
@@ -0,0 +1,146 @@
|
||||
<script setup>
|
||||
/**
|
||||
* FmsTree —— FMS 业务树(封装组件库 ui/Tree)。
|
||||
*
|
||||
* 渲染、交互、拖拽、右键菜单、键盘导航、无障碍全部由 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。
|
||||
*
|
||||
* 除下表声明的 props 外,其余 props 与事件(draggable / filter / disabled / contextMenu /
|
||||
* selectedKeys / checkedKeys / lazy / v-model:selected-keys …)经 $attrs 原样透传给 ui/Tree,
|
||||
* 事件名与 ui/Tree 保持一致。
|
||||
*
|
||||
* 注意:本组件始终向 ui/Tree 提供 #title 插槽(默认渲染翻译后的标题),因此 ui/Tree 内置的
|
||||
* filter 关键词高亮不再生效(过滤本身照常生效);需要高亮时在 #title 插槽内自行实现。
|
||||
*
|
||||
* 用法:
|
||||
* <FmsTree
|
||||
* :tree-data="moduleTree"
|
||||
* :expanded-keys="expandedKeys"
|
||||
* :selected-keys="selectedId ? [selectedId] : []"
|
||||
* :context-menu="buildContextMenu"
|
||||
* draggable
|
||||
* @select="onSelect"
|
||||
* @context-menu-click="onMenuClick"
|
||||
* >
|
||||
* <template #title="{ node, title }">
|
||||
* <span :class="{ 'is-disabled': Number(node.b_canuse) !== 1 }">{{ title }}</span>
|
||||
* </template>
|
||||
* </FmsTree>
|
||||
*/
|
||||
import { computed, ref } from 'vue'
|
||||
import { Tree } from '@/components/ui'
|
||||
import { translate } from '@/stores/i18n'
|
||||
|
||||
defineOptions({ inheritAttrs: false })
|
||||
|
||||
const props = defineProps({
|
||||
/** 业务树数据(b_id / b_name / children) */
|
||||
treeData: { type: Array, default: () => [] },
|
||||
/** 字段名覆盖:与 FMS 默认 { key: 'b_id', title: 'b_name', children: 'children' } 浅合并 */
|
||||
fieldNames: { type: Object, default: () => ({}) },
|
||||
/** 节点标题是否按 b_i18n 翻译 */
|
||||
translateTitle: { type: Boolean, default: true },
|
||||
/** 是否注入「全部分类」虚拟根节点 */
|
||||
showRoot: { type: Boolean, default: false },
|
||||
/** 虚拟根节点 key */
|
||||
rootKey: { type: [String, Number], default: '__fms_tree_root__' },
|
||||
/** 虚拟根节点标题,空则取多语言键 common.allCategories */
|
||||
rootTitle: { type: String, default: '' },
|
||||
/**
|
||||
* 展开键。undefined 表示非受控:无虚拟根时原样交给 ui/Tree(default-expand-all 等初始展开配置照常生效),
|
||||
* 有虚拟根时由本组件用内部状态维护,并照常抛出 update:expandedKeys。
|
||||
*/
|
||||
expandedKeys: { type: Array, default: undefined },
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:expandedKeys'])
|
||||
|
||||
/** showRoot 且调用方未受控时,本组件用内部状态持有展开键(始终不含 rootKey) */
|
||||
const internalExpandedKeys = ref([])
|
||||
|
||||
const fn = computed(() => ({
|
||||
key: 'b_id',
|
||||
title: 'b_name',
|
||||
children: 'children',
|
||||
...props.fieldNames,
|
||||
}))
|
||||
|
||||
/** 节点标题:b_i18n 有译文时取译文,否则按 b_name → b_code → b_id 回退 */
|
||||
function resolveNodeTitle(node) {
|
||||
if (node?.isFmsRoot) {
|
||||
return props.rootTitle || translate('common.allCategories', '全部分类')
|
||||
}
|
||||
const fallback = node?.[fn.value.title] || node?.b_code || node?.[fn.value.key] || ''
|
||||
return props.translateTitle ? translate(node?.b_i18n, fallback) : fallback
|
||||
}
|
||||
|
||||
/**
|
||||
* 实际渲染的树数据。
|
||||
* 虚拟根只在外层包一层、不复制业务节点:节点仍是业务树里的同一个对象,
|
||||
* 调用方对节点的改动(如启用勾选)能正常触发重渲染,按 b_id 反查父级/落点的逻辑也不会看到 rootKey。
|
||||
* 根是分组节点、不是业务叶子:显式 isLeaf=false,业务树为空时也照样显示折叠箭头
|
||||
* (否则空列表下根会被推断成叶子,界面上看不出这里还能展开)。
|
||||
*/
|
||||
const effectiveTreeData = computed(() => {
|
||||
if (!props.showRoot) return props.treeData
|
||||
return [
|
||||
{
|
||||
[fn.value.key]: props.rootKey,
|
||||
isFmsRoot: true,
|
||||
isLeaf: false,
|
||||
[fn.value.children]: props.treeData,
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
/** 根被用户手动收起后置 true:不再补回 rootKey,使折叠箭头能正常工作 */
|
||||
const rootCollapsed = ref(false)
|
||||
|
||||
const hasRootKey = (keys) => keys.some((key) => String(key) === String(props.rootKey))
|
||||
|
||||
/**
|
||||
* 虚拟根默认展开(调用方的展开键里不需要出现 rootKey);
|
||||
* 用户收起根之后不再强制补回,避免箭头点不动。
|
||||
*/
|
||||
const effectiveExpandedKeys = computed(() => {
|
||||
const keys = props.expandedKeys
|
||||
const withRoot = (list) =>
|
||||
props.showRoot && !rootCollapsed.value && !hasRootKey(list) ? [props.rootKey, ...list] : list
|
||||
if (keys === undefined) {
|
||||
return props.showRoot ? withRoot(internalExpandedKeys.value) : undefined
|
||||
}
|
||||
return withRoot(keys)
|
||||
})
|
||||
|
||||
function onExpandedKeysChange(keys) {
|
||||
// 用户收起/展开根:以本次的展开键是否含 rootKey 为准
|
||||
if (props.showRoot) rootCollapsed.value = !hasRootKey(keys)
|
||||
// 非受控时自己留一份(去掉根 key,根的展开由 effectiveExpandedKeys 统一补回)
|
||||
if (props.expandedKeys === undefined && props.showRoot) {
|
||||
internalExpandedKeys.value = keys.filter((key) => String(key) !== String(props.rootKey))
|
||||
}
|
||||
emit('update:expandedKeys', 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) }}
|
||||
</slot>
|
||||
</template>
|
||||
<template v-if="$slots.empty" #empty>
|
||||
<slot name="empty" />
|
||||
</template>
|
||||
</Tree>
|
||||
</template>
|
||||
Reference in new issue
Block a user