index.vue 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. <script lang="tsx">
  2. import type { ReplaceFields, Keys, CheckKeys, TreeActionType, TreeItem } from './types';
  3. import { defineComponent, reactive, computed, unref, ref, watchEffect, onMounted } from 'vue';
  4. import { Tree } from 'ant-design-vue';
  5. import { TreeIcon } from './TreeIcon';
  6. // import { DownOutlined } from '@ant-design/icons-vue';
  7. import { omit, get } from 'lodash-es';
  8. import { isFunction } from '/@/utils/is';
  9. import { extendSlots } from '/@/utils/helper/tsxHelper';
  10. import { useTree } from './useTree';
  11. import { useContextMenu, ContextMenuItem } from '/@/hooks/web/useContextMenu';
  12. import { useExpose } from '/@/hooks/core/useExpose';
  13. import { useDesign } from '/@/hooks/web/useDesign';
  14. import { basicProps } from './props';
  15. interface State {
  16. expandedKeys: Keys;
  17. selectedKeys: Keys;
  18. checkedKeys: CheckKeys;
  19. }
  20. export default defineComponent({
  21. name: 'BasicTree',
  22. props: basicProps,
  23. emits: ['update:expandedKeys', 'update:selectedKeys', 'update:value', 'get'],
  24. setup(props, { attrs, slots, emit }) {
  25. const state = reactive<State>({
  26. expandedKeys: props.expandedKeys || [],
  27. selectedKeys: props.selectedKeys || [],
  28. checkedKeys: props.checkedKeys || [],
  29. });
  30. const treeDataRef = ref<TreeItem[]>([]);
  31. const [createContextMenu] = useContextMenu();
  32. const { prefixCls } = useDesign('basic-tree');
  33. const getReplaceFields = computed(
  34. (): Required<ReplaceFields> => {
  35. const { replaceFields } = props;
  36. return {
  37. children: 'children',
  38. title: 'title',
  39. key: 'key',
  40. ...replaceFields,
  41. };
  42. }
  43. );
  44. // const getContentStyle = computed(
  45. // (): CSSProperties => {
  46. // const { actionList } = props;
  47. // const width = actionList.length * 18;
  48. // return {
  49. // width: `calc(100% - ${width}px)`,
  50. // };
  51. // }
  52. // );
  53. const getBindValues = computed(() => {
  54. let propsData = {
  55. blockNode: true,
  56. ...attrs,
  57. ...props,
  58. expandedKeys: state.expandedKeys,
  59. selectedKeys: state.selectedKeys,
  60. checkedKeys: state.checkedKeys,
  61. replaceFields: unref(getReplaceFields),
  62. 'onUpdate:expandedKeys': (v: Keys) => {
  63. state.expandedKeys = v;
  64. emit('update:expandedKeys', v);
  65. },
  66. 'onUpdate:selectedKeys': (v: Keys) => {
  67. state.selectedKeys = v;
  68. emit('update:selectedKeys', v);
  69. },
  70. onCheck: (v: CheckKeys, e) => {
  71. state.checkedKeys = v;
  72. console.log(e);
  73. emit('update:value', v);
  74. },
  75. onRightClick: handleRightClick,
  76. };
  77. propsData = omit(propsData, 'treeData');
  78. return propsData;
  79. });
  80. const getTreeData = computed((): TreeItem[] => unref(treeDataRef));
  81. const { deleteNodeByKey, insertNodeByKey, filterByLevel, updateNodeByKey } = useTree(
  82. treeDataRef,
  83. getReplaceFields
  84. );
  85. function getIcon(params: Recordable, icon?: string) {
  86. if (!icon) {
  87. if (props.renderIcon && isFunction(props.renderIcon)) {
  88. return props.renderIcon(params);
  89. }
  90. }
  91. return icon;
  92. }
  93. function renderAction(node: TreeItem) {
  94. const { actionList } = props;
  95. if (!actionList || actionList.length === 0) return;
  96. return actionList.map((item, index) => {
  97. return (
  98. <span key={index} class={`${prefixCls}__action`}>
  99. {item.render(node)}
  100. </span>
  101. );
  102. });
  103. }
  104. function renderTreeNode({ data, level }: { data: TreeItem[] | undefined; level: number }) {
  105. if (!data) {
  106. return null;
  107. }
  108. return data.map((item) => {
  109. const { title: titleField, key: keyField, children: childrenField } = unref(
  110. getReplaceFields
  111. );
  112. const propsData = omit(item, 'title');
  113. const icon = getIcon({ ...item, level }, item.icon);
  114. return (
  115. <Tree.TreeNode {...propsData} key={get(item, keyField)}>
  116. {{
  117. title: () => (
  118. <span class={`${prefixCls}-title`}>
  119. {icon && <TreeIcon icon={icon} />}
  120. <span
  121. class={`${prefixCls}__content`}
  122. // style={unref(getContentStyle)}
  123. >
  124. {get(item, titleField)}
  125. </span>
  126. <span class={`${prefixCls}__actions`}> {renderAction(item)}</span>
  127. </span>
  128. ),
  129. default: () =>
  130. renderTreeNode({ data: get(item, childrenField) || [], level: level + 1 }),
  131. }}
  132. </Tree.TreeNode>
  133. );
  134. });
  135. }
  136. async function handleRightClick({ event, node }: any) {
  137. const { rightMenuList: menuList = [], beforeRightClick } = props;
  138. let rightMenuList: ContextMenuItem[] = [];
  139. if (beforeRightClick && isFunction(beforeRightClick)) {
  140. rightMenuList = await beforeRightClick(node);
  141. } else {
  142. rightMenuList = menuList;
  143. }
  144. if (!rightMenuList.length) return;
  145. createContextMenu({
  146. event,
  147. items: rightMenuList,
  148. });
  149. }
  150. function setExpandedKeys(keys: string[]) {
  151. state.expandedKeys = keys;
  152. }
  153. function getExpandedKeys() {
  154. return state.expandedKeys;
  155. }
  156. function setSelectedKeys(keys: string[]) {
  157. state.selectedKeys = keys;
  158. }
  159. function getSelectedKeys() {
  160. return state.selectedKeys;
  161. }
  162. function setCheckedKeys(keys: CheckKeys) {
  163. state.checkedKeys = keys;
  164. }
  165. function getCheckedKeys() {
  166. return state.checkedKeys;
  167. }
  168. watchEffect(() => {
  169. treeDataRef.value = props.treeData as TreeItem[];
  170. state.expandedKeys = props.expandedKeys;
  171. state.selectedKeys = props.selectedKeys;
  172. state.checkedKeys = props.checkedKeys;
  173. });
  174. const instance: TreeActionType = {
  175. setExpandedKeys,
  176. getExpandedKeys,
  177. setSelectedKeys,
  178. getSelectedKeys,
  179. setCheckedKeys,
  180. getCheckedKeys,
  181. insertNodeByKey,
  182. deleteNodeByKey,
  183. updateNodeByKey,
  184. filterByLevel: (level: number) => {
  185. state.expandedKeys = filterByLevel(level);
  186. },
  187. };
  188. useExpose<TreeActionType>(instance);
  189. onMounted(() => {
  190. emit('get', instance);
  191. });
  192. return () => {
  193. return (
  194. <Tree {...unref(getBindValues)} showIcon={false} class={[prefixCls]}>
  195. {{
  196. // switcherIcon: () => <DownOutlined />,
  197. default: () => renderTreeNode({ data: unref(getTreeData), level: 1 }),
  198. ...extendSlots(slots),
  199. }}
  200. </Tree>
  201. );
  202. };
  203. },
  204. });
  205. </script>
  206. <style lang="less">
  207. @prefix-cls: ~'@{namespace}-basic-tree';
  208. .@{prefix-cls} {
  209. position: relative;
  210. .ant-tree-node-content-wrapper {
  211. position: relative;
  212. .ant-tree-title {
  213. position: absolute;
  214. left: 0;
  215. width: 100%;
  216. }
  217. }
  218. &-title {
  219. position: relative;
  220. display: flex;
  221. align-items: center;
  222. width: 100%;
  223. padding-right: 10px;
  224. &:hover {
  225. .@{prefix-cls}__action {
  226. visibility: visible;
  227. }
  228. }
  229. }
  230. &__content {
  231. display: inline-block;
  232. overflow: hidden;
  233. }
  234. &__actions {
  235. position: absolute;
  236. top: 2px;
  237. right: 2px;
  238. display: flex;
  239. }
  240. &__action {
  241. margin-left: 4px;
  242. visibility: hidden;
  243. }
  244. }
  245. </style>