diff --git a/docs/docs/zh-CN/components/tree.md b/docs/docs/zh-CN/components/tree.md new file mode 100644 index 00000000..5e822a31 --- /dev/null +++ b/docs/docs/zh-CN/components/tree.md @@ -0,0 +1,56 @@ +::: demo + + + + + +::: diff --git a/docs/src/layouts/Layout.vue b/docs/src/layouts/Layout.vue index 9c07a1c8..c6a3240f 100644 --- a/docs/src/layouts/Layout.vue +++ b/docs/src/layouts/Layout.vue @@ -18,14 +18,10 @@ style="margin-top: 0px; margin-bottom: 0px" >
  • - - 案例 - + 案例
  • - - 仓库 - + 仓库
  • -
      +
      • @@ -70,7 +71,7 @@ export default { setup() { const route = useRoute() const router = useRouter() - const currentPath = ref("hahaha") + const currentPath = ref('hahaha') watch( () => route.path, @@ -247,12 +248,19 @@ export default { title: '选项卡', subTitle: 'tab', path: '/zh-CN/components/tab', - },{ + }, + { id: 29, title: '图标选择', subTitle: 'iconPicker', path: '/zh-CN/components/iconPicker', }, + { + id: 30, + title: '树形组件', + subTitle: 'tree', + path: '/zh-CN/components/tree', + }, ] const selected = ref(1) @@ -284,4 +292,4 @@ export default { .layui-menu-docs .layui-menu-body-title .layui-font-gray { padding-left: 10px; } - \ No newline at end of file + diff --git a/docs/src/router/zh-CN.ts b/docs/src/router/zh-CN.ts index def04067..1c490c5a 100644 --- a/docs/src/router/zh-CN.ts +++ b/docs/src/router/zh-CN.ts @@ -148,7 +148,11 @@ const zhCN = [ path: '/zh-CN/components/iconPicker', component: () => import('../../docs/zh-CN/components/iconPicker.md'), meta: { title: '图标选择' }, - } + }, { + path: '/zh-CN/components/tree', + component: () => import('../../docs/zh-CN/components/tree.md'), + meta: { title: '树形组件' } + }, ], }, ] diff --git a/src/index.ts b/src/index.ts index e9c79c92..f3574bd7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ import type { App } from 'vue' import type { IDefineComponent, InstallOptions } from './module/type/index' -import "./css/layui.css" +import './css/layui.css' import LayAvatar from './module/avatar/index' import LayRadio from './module/radio/index' import LayButton from './module/button/index' @@ -10,27 +10,27 @@ import LayButtonGroup from './module/buttonGroup/index' import LayIcon from './module/icon/index' import LayIconPicker from './module/iconPicker/index' import LayCard from './module/card/index' -import LayLayout from "./module/layout/index" -import LaySide from "./module/side/index" -import LayBody from "./module/body/index" -import LayHeader from "./module/header/index" -import LayFooter from "./module/footer/index" -import LayLogo from "./module/logo/index" -import LayPanel from "./module/panel/index" -import LayProgress from "./module/progress/index" -import LayCol from "./module/col/index" -import LayRow from "./module/row/index" -import LayInput from "./module/input/index" -import LayBadge from "./module/badge/index" -import LayBlock from "./module/block/index" -import LayLine from "./module/line/index" -import LayTimeline from "./module/timeline/index" -import LayTimelineItem from "./module/timelineItem/index" -import LayTextarea from "./module/textarea/index" -import LaySwitch from "./module/switch/index" -import LayCollapse from "./module/collapse/index" -import LayCollapseItem from "./module/collapseItem/index" -import LayContainer from "./module/container/index" +import LayLayout from './module/layout/index' +import LaySide from './module/side/index' +import LayBody from './module/body/index' +import LayHeader from './module/header/index' +import LayFooter from './module/footer/index' +import LayLogo from './module/logo/index' +import LayPanel from './module/panel/index' +import LayProgress from './module/progress/index' +import LayCol from './module/col/index' +import LayRow from './module/row/index' +import LayInput from './module/input/index' +import LayBadge from './module/badge/index' +import LayBlock from './module/block/index' +import LayLine from './module/line/index' +import LayTimeline from './module/timeline/index' +import LayTimelineItem from './module/timelineItem/index' +import LayTextarea from './module/textarea/index' +import LaySwitch from './module/switch/index' +import LayCollapse from './module/collapse/index' +import LayCollapseItem from './module/collapseItem/index' +import LayContainer from './module/container/index' import LayMenu from './module/menu/index' import LayMenuItem from './module/menuItem/index' import LayMenuChildItem from './module/menuChildItem/index' @@ -49,6 +49,7 @@ import LayDropdown from './module/dropdown/index' import LayDropdownItem from './module/dropdownItem/index' import LayTab from './module/tab/index' import LayTabItem from './module/tabItem/index' +import LayTree from './module/tree/index' const components: Record = { LayRadio, @@ -97,11 +98,12 @@ const components: Record = { LayDropdownItem, LayTab, LayTabItem, - LayIconPicker + LayIconPicker, + LayTree } const install = (app: App, options?: InstallOptions): void => { - const _options = options; + const _options = options app.config.globalProperties.$PROOPTIONS = _options for (const key in components) { @@ -110,7 +112,7 @@ const install = (app: App, options?: InstallOptions): void => { } } -console.log("document Site : http://layui-vue.pearadmin.com") +console.log('document Site : http://layui-vue.pearadmin.com') export { LayRadio, @@ -122,7 +124,7 @@ export { LayBody, LayFooter, LayLogo, - LayPanel, + LayPanel, LayCard, LayProgress, LayButtonGroup, @@ -160,6 +162,7 @@ export { LayTab, LayTabItem, LayIconPicker, + LayTree, install, } diff --git a/src/module/tree/index.ts b/src/module/tree/index.ts new file mode 100644 index 00000000..b5482b91 --- /dev/null +++ b/src/module/tree/index.ts @@ -0,0 +1,9 @@ +import type { App } from 'vue' +import Component from './index.vue' +import type { IDefineComponent } from '../type/index' + +Component.install = (app: App) => { + app.component(Component.name || 'LayTree', Component) +} + +export default Component as IDefineComponent diff --git a/src/module/tree/index.vue b/src/module/tree/index.vue new file mode 100644 index 00000000..0c05bef7 --- /dev/null +++ b/src/module/tree/index.vue @@ -0,0 +1,287 @@ + + + + diff --git a/src/module/tree/props.ts b/src/module/tree/props.ts new file mode 100644 index 00000000..c87e5a38 --- /dev/null +++ b/src/module/tree/props.ts @@ -0,0 +1,14 @@ +import { TreeEmits, TreeProps } from '/@src/module/tree/type' + +export const treeProps = withDefaults(defineProps(), { + showCheckbox: false, + accordion: false, + onlyIconControl: false, + isJump: false, + showLine: true, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + edit: true +}) + +export const emit = defineEmits() diff --git a/src/module/tree/type.ts b/src/module/tree/type.ts new file mode 100644 index 00000000..fc2ff6f9 --- /dev/null +++ b/src/module/tree/type.ts @@ -0,0 +1,140 @@ +/** + * defineProps 暂不支付外部导入的复杂类型,这里备用后续迁移。 + * 暂时 在index.vue内部单独实现一份 + */ +import { VNode, VNodeChild } from 'vue' + +export interface TreeData { + /** + * 节点唯一索引值,用于对指定节点进行各类操作 + */ + id: () => (string | number) | string | number + /** + * 节点标题 + */ + title: (() => string) | string + /** + * 节点字段名 + */ + field: (() => string) | string + /** + * 子节点。支持设定选项同父节点 + */ + children: TreeData[] + /** + * 点击节点弹出新窗口对应的 url。需开启 isJump 参数 + */ + href: string | URL + /** + * 节点是否初始展开,默认 false + */ + spread: boolean + /** + * 节点是否初始为选中状态(如果开启复选框的话),默认 false + */ + checked: boolean + /** + * 节点是否为禁用状态。默认 false + */ + disabled: boolean +} + +type EditAction = 'add' | 'update' | 'del' + +type EditType = boolean | EditAction[] + +export interface TreeProps { + /** + * 数据源 + */ + data?: TreeData + /** + * 是否显示复选框 默认 false + */ + showCheckbox?: boolean + /** + * 是否开启节点的操作图标。默认 false。 + 若为 true,则默认显示“改删”图标 + 若为 数组,则可自由配置操作图标的显示状态和顺序,目前支持的操作图标有:add、update、del,如: + edit: ['add', 'update', 'del'] + */ + edit?: EditType + /** + * 是否开启手风琴模式,默认 false + */ + accordion?: boolean + /** + * 是否仅允许节点左侧图标控制展开收缩。默认 false(即点击节点本身也可控制)。若为 true,则只能通过节点左侧图标来展开收缩 + */ + onlyIconControl?: boolean + /** + * 是否允许点击节点时弹出新窗口跳转。默认 false,若开启,需在节点数据中设定 link 参数(值为 url 格式 + */ + isJump?: boolean + /** + * 是否开启连接线。默认 true,若设为 false,则节点左侧出现三角图标。 + */ + showLine?: boolean + /** + * 自定义各类默认文本,目前支持以下设定: + */ + text?: { + /** + * 节点默认名称 + */ + defaultNodeName?: () => string | string + /** + * 数据为空时的提示文本 + */ + none?: () => string | string | VNode | Element + } +} + +export interface TreeNode { + /** + * 当前点击的节点数据 + */ + data: TreeData + /** + * 节点的展开状态 + */ + state: 'open' | 'close' | 'normal' + /** + * 当前节点元素 + */ + elem: Element | VNode | VNodeChild +} + +export interface TreeEmits { + /** + * 节点被点击后触发 + * @param e 事件 + * @param treeNode + */ + (e: 'on-click', treeNode: TreeNode): void + /** + * 点击复选框时触发 + * @param e 事件 + * @param treeNode + */ + (e: 'on-check', treeNode: TreeNode): void + /** + * 操作节点的回调 + * @param e 事件 + * @param treeNode + */ + (e: 'on-operate', treeNode: TreeNode): void +} + + +export interface TreeExpose { + /** + * 返回选中的节点数据 + * 一般来说,v-model就够使了,但不排除需要直接获取其值的时候,此时通过该方法获取 + * eg: treeRef.getChecked() + */ + getChecked: () => TreeData +} + +// 设置节点勾选 setChecked 变为v-model控制 +// 实例重载 reload 变为v-model控制