├── .eslintignore ├── docs ├── favicon.ico ├── index.html └── assets │ └── index.aa6a4930.js ├── public └── favicon.ico ├── src ├── components │ ├── emitter.js │ ├── collapse-tree.vue │ └── collapse-tree-item.vue ├── index.js └── examples │ ├── main.js │ ├── App.vue │ └── Example.vue ├── .editorconfig ├── .gitignore ├── index.html ├── LICENSE ├── package.json ├── vite.config.js ├── README.md └── .eslintrc.js /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | README.md 4 | -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kooriookami/vue-collapse-tree/HEAD/docs/favicon.ico -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kooriookami/vue-collapse-tree/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/components/emitter.js: -------------------------------------------------------------------------------- 1 | import mitt from 'mitt'; 2 | 3 | const emitter = mitt(); 4 | 5 | export default emitter; 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | quote_type = single 11 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import CollapseTree from './components/collapse-tree'; 2 | import CollapseTreeItem from './components/collapse-tree-item'; 3 | 4 | export default { 5 | install: app => { 6 | app.component('CollapseTree', CollapseTree); 7 | app.component('CollapseTreeItem', CollapseTreeItem); 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/examples/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import App from './App.vue'; 3 | import ElementPlus from 'element-plus'; 4 | import 'element-plus/dist/index.css'; 5 | import zhCn from 'element-plus/es/locale/lang/zh-cn'; 6 | import 'normalize.css'; 7 | import CollapseTree from '../index'; 8 | 9 | const app = createApp(App); 10 | app.use(ElementPlus, { 11 | locale: zhCn, 12 | }); 13 | app.use(CollapseTree); 14 | app.mount('#app'); 15 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/examples/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 18 | 19 | 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-present kooriookami 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-collapse-tree", 3 | "version": "1.0.1", 4 | "main": "umd/index.js", 5 | "module": "es/index.js", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "build:website": "vite build -- website", 10 | "preview": "vite preview", 11 | "publish": "npm publish dist/ --registry https://registry.npmjs.org", 12 | "lint": "eslint ./src --ext .vue,.js,.ts,.jsx,.tsx", 13 | "lint-fix": "eslint --fix ./src --ext .vue,.js,.ts,.jsx,.tsx" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/kooriookami/vue-collapse-tree.git" 18 | }, 19 | "author": "kooriookami", 20 | "license": "ISC", 21 | "dependencies": { 22 | "@element-plus/icons-vue": "^1.0.0", 23 | "element-plus": "^2.0.4", 24 | "element-resize-detector": "^1.2.4", 25 | "mitt": "^3.0.0", 26 | "normalize.css": "^8.0.1", 27 | "vue": "^3.2.31" 28 | }, 29 | "devDependencies": { 30 | "@vitejs/plugin-vue": "^2.2.4", 31 | "eslint": "^8.10.0", 32 | "eslint-define-config": "^1.2.5", 33 | "eslint-plugin-vue": "^8.5.0", 34 | "rollup-plugin-copy": "^3.4.0", 35 | "sass": "^1.49.9", 36 | "vite": "^2.8.6" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import vue from '@vitejs/plugin-vue'; 3 | import copy from 'rollup-plugin-copy'; 4 | import path from 'path'; 5 | 6 | const buildLib = { 7 | lib: { 8 | entry: path.resolve(__dirname, 'src/index.js'), 9 | name: 'CollapseTree', 10 | fileName: format => `${format}/index.js`, 11 | }, 12 | rollupOptions: { 13 | // 确保外部化处理那些你不想打包进库的依赖 14 | external: ['vue'], 15 | output: { 16 | // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量 17 | globals: { 18 | vue: 'Vue', 19 | }, 20 | }, 21 | plugins: [ 22 | copy({ 23 | targets: [ 24 | { src: 'LICENSE', dest: 'dist' }, 25 | { src: 'README.md', dest: 'dist' }, 26 | { src: 'package.json', dest: 'dist' }, 27 | ], 28 | hook: 'writeBundle', 29 | }), 30 | ], 31 | }, 32 | }; 33 | 34 | const buildWebsite = { 35 | outDir: 'docs', 36 | }; 37 | 38 | export default defineConfig({ 39 | base: './', 40 | plugins: [vue()], 41 | resolve: { 42 | alias: { 43 | '@': path.resolve(__dirname, 'src'), 44 | }, 45 | extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'], // 扩展了.vue后缀 46 | }, 47 | server: { 48 | host: '0.0.0.0', 49 | port: 8020, 50 | }, 51 | build: process.argv.includes('website') ? buildWebsite : buildLib, 52 | }); 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue Collapse Tree 2 | 3 | 这是一个基于 Vue3 的折叠层级树。 4 | 5 | ## 使用说明 6 | 7 | ```npm i vue-collapse-tree``` 8 | 9 | ```js 10 | import { createApp } from "vue"; 11 | import App from "./App.vue"; 12 | import CollapseTree from "vue-collapse-tree"; 13 | import "vue-collapse-tree/style.css"; 14 | 15 | const app = createApp(App); 16 | app.use(CollapseTree); 17 | app.mount("#app"); 18 | ``` 19 | 20 | ## 在线演示 21 | 22 | [在线演示](https://kooriookami.github.io/vue-collapse-tree/) 23 | 24 | ## 示例代码 25 | 26 | [示例代码](src/examples/Example.vue) 27 | 28 | ## Collapse Tree 属性 29 | 30 | | 参数 | 说明 | 类型 | 默认值 | 31 | |------------------------|---------|--------|------| 32 | | line | 是否显示层级线 | boolean | true | 33 | | line-offset-height | 线高度偏移 | number | 20 | 34 | | line-offset-top | 线距顶偏移 | number | 20 | 35 | | default-expanded-level | 默认展开层级 | number | 1 | 36 | | indent | 层级缩进 | number | 40 | 37 | | icon-placeholder | 空图标占位 | boolean | true | 38 | 39 | ## Collapse Tree 插槽 40 | 41 | | 插槽名 | 说明 | 42 | |--------|-----------------------------| 43 | | — | 自定义默认内容 | 44 | 45 | ## Collapse Tree Item 属性 46 | 47 | | 参数 | 说明 | 类型 | 默认值 | 48 | |------|------|---------|-------| 49 | | show | 默认展开 | boolean | false | 50 | 51 | ## Collapse Tree Item 插槽 52 | 53 | | 插槽名 | 说明 | 子标签 | 54 | |--------|-------------|------------------| 55 | | — | 自定义层级内容 | CollapseTreeItem | 56 | | header | 自定义层级头部内容 | — | 57 | | icon | 自定义折叠图标内容 | — | 58 | -------------------------------------------------------------------------------- /src/components/collapse-tree.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 67 | 68 | 73 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('eslint-define-config'); 2 | 3 | module.exports = defineConfig({ 4 | root: true, 5 | parser: 'vue-eslint-parser', 6 | parserOptions: { 7 | sourceType: 'module', 8 | ecmaFeatures: { 9 | jsx: true, 10 | }, 11 | }, 12 | env: { 13 | browser: true, 14 | node: true, 15 | }, 16 | extends: [ 17 | 'plugin:vue/recommended', 18 | ], 19 | rules: { 20 | // js 21 | 'eol-last': 'error', 22 | 'no-trailing-spaces': 'error', 23 | 'comma-style': ['error', 'last'], 24 | 'comma-dangle': ['error', 'always-multiline'], 25 | 'no-multi-spaces': 'error', 26 | quotes: ['error', 'single', { avoidEscape: true, allowTemplateLiterals: true }], 27 | 'object-curly-spacing': ['error', 'always'], 28 | 'arrow-parens': ['error', 'as-needed'], 29 | 'spaced-comment': ['error', 'always'], 30 | 'semi': [2, 'always'], 31 | // vue 32 | 'vue/no-v-html': 'off', 33 | 'vue/singleline-html-element-content-newline': 'off', 34 | 'vue/multi-word-component-names': 'off', 35 | 'vue/max-attributes-per-line': ['error', { singleline: 3, multiline: 1 }], 36 | 'vue/script-indent': ['error', 2, { baseIndent: 1 }], 37 | 'vue/order-in-components': 'off', 38 | 'vue/require-default-prop': 'off', 39 | 'vue/html-closing-bracket-spacing': 'error', 40 | 'vue/require-prop-types': 'off', 41 | 'vue/prop-name-casing': 'off', 42 | 'vue/no-template-shadow': 'off', 43 | 'vue/no-side-effects-in-computed-properties': 'off', 44 | 'vue/no-mutating-props': 'off', 45 | 'vue/no-use-v-if-with-v-for': 'off', 46 | 'vue/require-v-for-key': 'off', 47 | 'vue/valid-v-for': 'off', 48 | 'vue/no-unused-vars': 'off', 49 | }, 50 | }); 51 | -------------------------------------------------------------------------------- /src/examples/Example.vue: -------------------------------------------------------------------------------- 1 | 81 | 82 | 105 | 106 | 128 | -------------------------------------------------------------------------------- /src/components/collapse-tree-item.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 139 | 140 | 193 | -------------------------------------------------------------------------------- /docs/assets/index.aa6a4930.js: -------------------------------------------------------------------------------- 1 | import{r as u,o as V,c as L,a as v,b as A,w as a,d as l,e as _,f as U,m as z,g as y,p as D,h as K,t as j,i as S,j as q,k as H,l as I,n as F,q as b,s as P,u as M,v as G,x as J,y as Q,z as W,A as X,B as Y}from"./vendor.4211d153.js";const Z=function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const e of document.querySelectorAll('link[rel="modulepreload"]'))o(e);new MutationObserver(e=>{for(const s of e)if(s.type==="childList")for(const i of s.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&o(i)}).observe(document,{childList:!0,subtree:!0});function d(e){const s={};return e.integrity&&(s.integrity=e.integrity),e.referrerpolicy&&(s.referrerPolicy=e.referrerpolicy),e.crossorigin==="use-credentials"?s.credentials="include":e.crossorigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function o(e){if(e.ep)return;e.ep=!0;const s=d(e);fetch(e.href,s)}};Z();var w=(r,t)=>{const d=r.__vccOpts||r;for(const[o,e]of t)d[o]=e;return d};const ee={name:"Example",data(){return{form:{line:!0,lineOffsetHeight:20,lineOffsetTop:20,defaultExpandedLevel:1,indent:40,iconPlaceholder:!0},key:0}},methods:{refresh(){this.key++}}},te={class:"example-container"},ne={class:"examples"},le=_(" 1-1 "),oe=_(" 1-1-1 "),se=_(" 1-2 "),ae=_("1-2-1"),re=_("1-2-1-1"),ie={class:"form"},de=_("\u54CD\u5E94\u5F0F\u5C5E\u6027"),ce=_("\u975E\u54CD\u5E94\u5F0F\u5C5E\u6027"),ue={class:"button-group"},pe=_("\u91CD\u65B0\u6E32\u67D3");function fe(r,t,d,o,e,s){const i=u("collapse-tree-item"),m=u("el-button"),N=u("el-tag"),E=u("el-alert"),k=u("collapse-tree"),O=u("el-divider"),g=u("el-switch"),f=u("el-form-item"),C=u("el-slider"),$=u("el-input-number"),B=u("el-form");return V(),L("div",te,[v("div",ne,[(V(),A(k,{key:e.key,line:e.form.line,"line-offset-height":e.form.lineOffsetHeight,"line-offset-top":e.form.lineOffsetTop,"default-expanded-level":e.form.defaultExpandedLevel,indent:e.form.indent,"icon-placeholder":e.form.iconPlaceholder},{default:a(()=>[l(i,null,{header:a(()=>[le]),default:a(()=>[l(i,null,{header:a(()=>[oe]),_:1})]),_:1}),l(i,null,{header:a(()=>[se]),default:a(()=>[l(i,null,{header:a(()=>[l(m,null,{default:a(()=>[ae]),_:1})]),default:a(()=>[l(i,null,{header:a(()=>[l(N,null,{default:a(()=>[re]),_:1})]),_:1})]),_:1}),l(i,null,{header:a(()=>[l(E,{title:"1-2-2",type:"success",effect:"dark",closable:!1})]),_:1})]),_:1})]),_:1},8,["line","line-offset-height","line-offset-top","default-expanded-level","indent","icon-placeholder"]))]),v("div",ie,[l(B,{"label-width":"auto"},{default:a(()=>[l(O,null,{default:a(()=>[de]),_:1}),l(f,{label:"\u5C42\u7EA7\u7EBF"},{default:a(()=>[l(g,{modelValue:e.form.line,"onUpdate:modelValue":t[0]||(t[0]=c=>e.form.line=c)},null,8,["modelValue"])]),_:1}),l(f,{label:"\u5C42\u7EA7\u7F29\u8FDB"},{default:a(()=>[l(C,{modelValue:e.form.indent,"onUpdate:modelValue":t[1]||(t[1]=c=>e.form.indent=c)},null,8,["modelValue"])]),_:1}),l(f,{label:"\u7A7A\u56FE\u6807\u5360\u4F4D"},{default:a(()=>[l(g,{modelValue:e.form.iconPlaceholder,"onUpdate:modelValue":t[2]||(t[2]=c=>e.form.iconPlaceholder=c)},null,8,["modelValue"])]),_:1}),l(O,null,{default:a(()=>[ce]),_:1}),l(f,{label:"\u7EBF\u9AD8\u5EA6\u504F\u79FB"},{default:a(()=>[l(C,{modelValue:e.form.lineOffsetHeight,"onUpdate:modelValue":t[3]||(t[3]=c=>e.form.lineOffsetHeight=c)},null,8,["modelValue"])]),_:1}),l(f,{label:"\u7EBF\u8DDD\u9876\u504F\u79FB"},{default:a(()=>[l(C,{modelValue:e.form.lineOffsetTop,"onUpdate:modelValue":t[4]||(t[4]=c=>e.form.lineOffsetTop=c)},null,8,["modelValue"])]),_:1}),l(f,{label:"\u9ED8\u8BA4\u5C55\u5F00\u5C42\u7EA7"},{default:a(()=>[l($,{modelValue:e.form.defaultExpandedLevel,"onUpdate:modelValue":t[5]||(t[5]=c=>e.form.defaultExpandedLevel=c),min:0,precision:"0"},null,8,["modelValue"])]),_:1})]),_:1}),v("div",ue,[l(m,{style:{width:"100%"},type:"primary",onClick:s.refresh},{default:a(()=>[pe]),_:1},8,["onClick"])])])])}var _e=w(ee,[["render",fe],["__scopeId","data-v-5e384557"]]);const me=U({name:"App",components:{Example:_e}}),he={class:"app-container"};function ve(r,t,d,o,e,s){const i=u("Example");return V(),L("div",he,[l(i)])}var ge=w(me,[["render",ve]]);const R=z();const Ce={name:"CollapseTree",props:{line:{type:Boolean,default:!0},lineOffsetHeight:{type:Number,default:20},lineOffsetTop:{type:Number,default:20},defaultExpandedLevel:{type:Number,default:1},indent:{type:Number,default:40},iconPlaceholder:{type:Boolean,default:!0}},setup(r){const t=y(),d=y(null);D("collapseTreeKey",K(j(r)));const o=()=>{R.emit("update")};return S(()=>{t.value=q(),t.value.listenTo(d.value,()=>{o()})}),H(()=>{t.value.removeAllListeners(d.value)}),{root:d}}},ye={ref:"root",class:"collapse-tree"};function Te(r,t,d,o,e,s){return V(),L("div",ye,[I(r.$slots,"default",{},void 0,!0)],512)}var be=w(Ce,[["render",Te],["__scopeId","data-v-a52dc912"]]);const Ve={name:"CollapseTreeItem",components:{CaretRight:F},props:{show:{type:Boolean,default:!1}},setup(r,{slots:t}){const d=Q(),o=J("collapseTreeKey"),e=y(null),s=y(!1),i=y(0),m=y(0),N=b(()=>r.show),E=()=>{s.value=N.value||$.value<=o.defaultExpandedLevel},k=()=>{const n=g();if(!n)return;const p=n.type.name,h=n.vnode.el.getBoundingClientRect(),T=d.vnode.el.getBoundingClientRect();p==="CollapseTreeItem"&&(i.value=T.top-h.top-o.lineOffsetHeight,m.value=T.height-o.lineOffsetTop)},O=()=>{s.value=!s.value},g=(n=d)=>{var h;const p=(h=n==null?void 0:n.parent)==null?void 0:h.type.name;return["CollapseTree","CollapseTreeItem"].includes(p)?n.parent:(n==null?void 0:n.parent)?g(n.parent):null},f=(n=d,p=1)=>{var T;const h=(T=n==null?void 0:n.parent)==null?void 0:T.type.name;return h==="CollapseTree"?p:(n==null?void 0:n.parent)?(h==="CollapseTreeItem"&&(p+=1),f(n.parent,p)):null},C=b(()=>{const n=g();return(n==null?void 0:n.type.name)==="CollapseTree"}),$=b(()=>f()),B=b(()=>({paddingLeft:C.value?"0":`${o.indent}px`,"--line-display":o.line&&!C.value?"block":"none","--line-height":`${i.value}px`,"--line-width":`${o.indent/2}px`,"--line-left":`${o.indent/2}px`,"--item-height":`${m.value}px`})),c=b(()=>{var p;const n=!!((p=t.default)==null?void 0:p.call(t));return{cursor:n?"pointer":"",transform:s.value?"rotate(90deg)":"",visibility:n?"":"hidden",display:n||o.iconPlaceholder?"":"none"}});return S(()=>{E(),R.on("update",k)}),H(()=>{R.off("update",k)}),{item:e,showContent:s,itemStyle:B,iconStyle:c,handleClickIcon:O}}},ke={class:"collapse-header"},Oe={class:"header-content"},Ie={class:"collapse-content"};function Le(r,t,d,o,e,s){const i=u("CaretRight");return V(),L("div",{ref:"item",class:"collapse-tree-item",style:P(o.itemStyle)},[v("div",ke,[v("div",{class:"header-icon",style:P(o.iconStyle),onClick:t[0]||(t[0]=(...m)=>o.handleClickIcon&&o.handleClickIcon(...m))},[I(r.$slots,"icon",{},()=>[l(i,{class:"collapse-icon"})],!0)],4),v("div",Oe,[I(r.$slots,"header",{},void 0,!0)])]),M(v("div",Ie,[I(r.$slots,"default",{},void 0,!0)],512),[[G,o.showContent]])],4)}var we=w(Ve,[["render",Le],["__scopeId","data-v-26f1c488"]]),Ne={install:r=>{r.component("CollapseTree",be),r.component("CollapseTreeItem",we)}};const x=W(ge);x.use(X,{locale:Y});x.use(Ne);x.mount("#app"); 2 | --------------------------------------------------------------------------------