Browse Source

feat(sidenarbar): 新增侧边栏导航组件

suzigang 3 years ago
parent
commit
0cc86c799d

+ 35 - 0
src/config.json

@@ -633,6 +633,41 @@
           "exportEmpty": true,
           "exportEmptyTaro": true,
           "author": "haiweilian"
+        },
+        {
+          "version": "3.1.15",
+          "name": "SideNavBar",
+          "type": "component",
+          "cName": "侧边栏导航",
+          "desc": "垂直展示的导航栏,用于内容选择和切换",
+          "sort": 14,
+          "taro": true,
+          "show": true,
+          "author": "szg2008"
+        },
+        {
+          "version": "3.1.15",
+          "name": "SideNavBarItem",
+          "type": "component",
+          "cName": "侧边栏导航子组件",
+          "desc": "",
+          "sort": 15,
+          "exportEmpty": true,
+          "taro": true,
+          "show": false,
+          "author": "szg2008"
+        },
+        {
+          "version": "3.1.15",
+          "name": "SubSideNavBar",
+          "type": "component",
+          "cName": "侧边栏导航子组件",
+          "desc": "",
+          "sort": 16,
+          "exportEmpty": true,
+          "taro": true,
+          "show": false,
+          "author": "szg2008"
         }
       ]
     },

+ 130 - 0
src/packages/__VUE/sidenavbar/demo.vue

@@ -0,0 +1,130 @@
+<template>
+  <div class="demo">
+    <h2>基本用法</h2>
+    <nut-cell @click="handleClick1">
+      <span><label>右侧</label></span>
+    </nut-cell>
+    <nut-popup position="right" v-model:visible="show1" :style="{ width, height }">
+      <nut-sidenavbar>
+        <nut-subsidenavbar title="智能城市AI" ikey="6">
+          <nut-subsidenavbar title="人体识别1" ikey="9">
+            <nut-sidenavbaritem ikey="10" title="人体检测1"></nut-sidenavbaritem>
+            <nut-sidenavbaritem ikey="11" title="细粒度人像分割1"></nut-sidenavbaritem>
+          </nut-subsidenavbar>
+          <nut-subsidenavbar title="人体识别2" ikey="12">
+            <nut-sidenavbaritem ikey="13" title="人体检测2"></nut-sidenavbaritem>
+            <nut-sidenavbaritem ikey="14" title="细粒度人像分割2"></nut-sidenavbaritem>
+          </nut-subsidenavbar>
+        </nut-subsidenavbar>
+      </nut-sidenavbar>
+    </nut-popup>
+    <nut-cell @click="handleClick2">
+      <span><label>左侧</label></span>
+    </nut-cell>
+    <nut-popup position="left" v-model:visible="show2" :style="{ width, height }">
+      <nut-sidenavbar>
+        <nut-subsidenavbar title="图像理解" ikey="3" :open="false">
+          <nut-sidenavbaritem ikey="4" title="菜品识别"></nut-sidenavbaritem>
+          <nut-sidenavbaritem ikey="5" title="拍照购"></nut-sidenavbaritem>
+        </nut-subsidenavbar>
+        <nut-subsidenavbar title="自然语言处理" ikey="12">
+          <nut-sidenavbaritem ikey="13" title="词法分析"></nut-sidenavbaritem>
+          <nut-sidenavbaritem ikey="14" title="句法分析"></nut-sidenavbaritem>
+        </nut-subsidenavbar>
+      </nut-sidenavbar>
+    </nut-popup>
+    <h2>导航嵌套(建议最多三层),点击第一条回调</h2>
+    <div>
+      <nut-cell @click="handleClick3">
+        <span><label>显示</label></span>
+      </nut-cell>
+      <nut-popup position="right" v-model:visible="show3" :style="{ width, height }">
+        <nut-sidenavbar :show="show3">
+          <nut-sidenavbaritem ikey="1" title="人脸识别" @click="handleClick4('人脸识别')"></nut-sidenavbaritem>
+          <nut-sidenavbaritem ikey="2" title="云存自然语言处理"></nut-sidenavbaritem>
+          <nut-subsidenavbar title="图像理解" ikey="3" :open="false">
+            <nut-sidenavbaritem ikey="4" title="菜品识别"></nut-sidenavbaritem>
+            <nut-sidenavbaritem ikey="5" title="拍照购"></nut-sidenavbaritem>
+          </nut-subsidenavbar>
+          <nut-subsidenavbar title="智能城市AI" ikey="6">
+            <nut-sidenavbaritem ikey="7" title="企业风险预警模型"></nut-sidenavbaritem>
+            <nut-sidenavbaritem ikey="8" title="水质量检测"></nut-sidenavbaritem>
+            <nut-subsidenavbar title="人体识别" ikey="9">
+              <nut-sidenavbaritem ikey="10" title="人体检测"></nut-sidenavbaritem>
+              <nut-sidenavbaritem ikey="11" title="细粒度人像分割"></nut-sidenavbaritem>
+            </nut-subsidenavbar>
+          </nut-subsidenavbar>
+          <nut-subsidenavbar title="自然语言处理" ikey="12">
+            <nut-sidenavbaritem ikey="13" title="词法分析"></nut-sidenavbaritem>
+            <nut-sidenavbaritem ikey="14" title="句法分析"></nut-sidenavbaritem>
+          </nut-subsidenavbar>
+          <nut-subsidenavbar v-for="item in navs" :key="item.id" :title="item.name" :ikey="item.id">
+            <nut-sidenavbaritem v-for="citem in item.arr" :key="citem.id" :title="citem.name"></nut-sidenavbaritem>
+          </nut-subsidenavbar>
+        </nut-sidenavbar>
+      </nut-popup>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import { reactive, toRefs } from 'vue';
+import { createComponent } from '../../utils/create';
+const { createDemo } = createComponent('sidenavbar');
+import { Toast } from '@/packages/nutui.vue';
+export default createDemo({
+  setup() {
+    const state = reactive({
+      show1: false,
+      show2: false,
+      show3: false,
+      width: '80%',
+      height: '100%',
+      navs: [] as any[]
+    });
+
+    const handleClick1 = () => {
+      state.show1 = true;
+    };
+
+    const handleClick2 = () => {
+      state.show2 = true;
+    };
+
+    const handleClick3 = () => {
+      state.show3 = true;
+      setTimeout(() => {
+        state.navs = [
+          {
+            id: 16,
+            name: '异步abc16',
+            arr: [{ pid: 16, id: 17, name: 'abc16-id17' }]
+          },
+          {
+            id: 17,
+            name: '异步abc17',
+            arr: [{ pid: 17, id: 18, name: 'abc17-id18' }]
+          }
+        ];
+      }, 2000);
+    };
+
+    const handleClick4 = (msg: string) => {
+      Toast.text(msg);
+    };
+
+    return {
+      ...toRefs(state),
+      handleClick1,
+      handleClick2,
+      handleClick3,
+      handleClick4
+    };
+  }
+});
+</script>
+
+<style lang="scss" scoped>
+.demo {
+}
+</style>

+ 168 - 0
src/packages/__VUE/sidenavbar/doc.md

@@ -0,0 +1,168 @@
+# SideNavBar 侧边栏导航
+
+### 介绍
+
+用于内容选择和切换
+
+### 安装
+
+``` javascript
+import { createApp } from 'vue';
+// vue
+import { SideNavBar, SubSideNavBar, SideNavBarItem } from '@nutui/nutui';
+// taro
+import { SideNavBar, SubSideNavBar, SideNavBarItem } from '@nutui/nutui-taro';
+
+const app = createApp();
+app.use(SideNavBar).use(SubSideNavBar).use(SideNavBarItem);
+```
+
+### 基本用法
+
+``` html
+<nut-cell @click="handleClick1">
+  <span><label>右侧</label></span>
+</nut-cell>
+<nut-popup position="right" v-model:visible="show1" :style="{ width, height }">
+  <nut-sidenavbar>
+    <nut-subsidenavbar title="智能城市AI" ikey="6">
+      <nut-subsidenavbar title="人体识别1" ikey="9">
+        <nut-sidenavbaritem ikey="10" title="人体检测1"></nut-sidenavbaritem>
+        <nut-sidenavbaritem ikey="11" title="细粒度人像分割1"></nut-sidenavbaritem>
+      </nut-subsidenavbar>
+      <nut-subsidenavbar title="人体识别2" ikey="12">
+        <nut-sidenavbaritem ikey="13" title="人体检测2"></nut-sidenavbaritem>
+        <nut-sidenavbaritem ikey="14" title="细粒度人像分割2"></nut-sidenavbaritem>
+      </nut-subsidenavbar>
+    </nut-subsidenavbar>
+  </nut-sidenavbar>
+</nut-popup>
+```
+``` ts
+setup() {
+  const state = reactive({
+    show1: false,
+    width: '80%',
+    height: '100%',
+  });
+
+  const handleClick1 = () => {
+    state.show1 = true;
+  };
+
+  return {
+    ...toRefs(state),
+    handleClick1
+  };
+}
+```
+
+### 嵌套(建议最多三层)
+> 小程序暂不支持异步加载
+
+``` html
+<nut-cell @click="handleClick3">
+  <span><label>显示</label></span>
+</nut-cell>
+<nut-popup position="right" v-model:visible="show3" :style="{ width, height }">
+  <nut-sidenavbar :show="show3">
+    <nut-sidenavbaritem ikey="1" title="人脸识别" @click="handleClick4('人脸识别')"></nut-sidenavbaritem>
+    <nut-sidenavbaritem ikey="2" title="云存自然语言处理"></nut-sidenavbaritem>
+    <nut-subsidenavbar title="图像理解" ikey="3" :open="false">
+      <nut-sidenavbaritem ikey="4" title="菜品识别"></nut-sidenavbaritem>
+      <nut-sidenavbaritem ikey="5" title="拍照购"></nut-sidenavbaritem>
+    </nut-subsidenavbar>
+    <nut-subsidenavbar title="智能城市AI" ikey="6">
+      <nut-sidenavbaritem ikey="7" title="企业风险预警模型"></nut-sidenavbaritem>
+      <nut-sidenavbaritem ikey="8" title="水质量检测"></nut-sidenavbaritem>
+      <nut-subsidenavbar title="人体识别" ikey="9">
+        <nut-sidenavbaritem ikey="10" title="人体检测"></nut-sidenavbaritem>
+        <nut-sidenavbaritem ikey="11" title="细粒度人像分割"></nut-sidenavbaritem>
+      </nut-subsidenavbar>
+    </nut-subsidenavbar>
+    <nut-subsidenavbar title="自然语言处理" ikey="12">
+      <nut-sidenavbaritem ikey="13" title="词法分析"></nut-sidenavbaritem>
+      <nut-sidenavbaritem ikey="14" title="句法分析"></nut-sidenavbaritem>
+    </nut-subsidenavbar>
+    <nut-subsidenavbar v-for="item in navs" :key="item.id" :title="item.name" :ikey="item.id">
+      <nut-sidenavbaritem v-for="citem in item.arr" :key="citem.id" :title="citem.name"></nut-sidenavbaritem>
+    </nut-subsidenavbar>
+  </nut-sidenavbar>
+</nut-popup>
+```
+``` ts
+setup() {
+  const state = reactive({
+    show3: false,
+    width: '80%',
+    height: '100%',
+    navs: [] as any[]
+  });
+
+  const handleClick3 = () => {
+    state.show3 = true;
+    setTimeout(() => {
+      state.navs = [
+        {
+          id: 16,
+          name: '异步abc16',
+          arr: [{ pid: 16, id: 17, name: 'abc16-id17' }]
+        },
+        {
+          id: 17,
+          name: '异步abc17',
+          arr: [{ pid: 17, id: 18, name: 'abc17-id18' }]
+        }
+      ];
+    }, 2000);
+  };
+
+  const handleClick4 = (msg: string) => {
+    Toast.text(msg)
+  }
+
+  return {
+    ...toRefs(state),
+    handleClick3,
+    handleClick4
+  };
+}
+```
+
+## API
+
+### SideNavBar
+
+| 字段                   | 说明                                                             | 类型    | 默认值 |
+|------------------------|----------------------------------------------------------------|---------|------|
+| offset                 | 导航缩进宽度                                                    | Number、String  | `15`
+
+### SubSideNavBar
+
+| 字段                   | 说明                                                             | 类型    | 默认值 |
+|------------------------|----------------------------------------------------------------|---------|------|
+| title                 | 导航标题                                                    | String  | ``
+| ikey                 | 导航唯一标识                                                    | String、Number  | ``
+| open                 | 导航是否默认展开                                                    | Boolean  | `true`
+
+### SideNavBarItem
+
+| 字段                   | 说明                                                             | 类型    | 默认值 |
+|------------------------|----------------------------------------------------------------|---------|------|
+| title                 | 导航标题                                                    | String  | `15`
+| ikey                 | 导航唯一标识                                                    | String、Number  | ``
+
+
+### SubSideNavBar Event
+
+| 名称  | 说明     | 回调参数    |
+|-------|----------|-------------|
+| title-click | 导航点击 | - |
+
+### SideNavBarItem Event
+
+| 名称  | 说明     | 回调参数    |
+|-------|----------|-------------|
+| click | 导航点击 | - |
+
+

+ 14 - 0
src/packages/__VUE/sidenavbar/index.scss

@@ -0,0 +1,14 @@
+.nut-sidenavbar {
+  height: 100%;
+  overflow: auto;
+  display: block;
+  &__content {
+    position: relative;
+    background-color: $sidenavbar-content-bg-color;
+    display: block;
+    &__list {
+      width: 100%;
+      display: block;
+    }
+  }
+}

+ 81 - 0
src/packages/__VUE/sidenavbar/index.taro.vue

@@ -0,0 +1,81 @@
+<template>
+  <view :class="classes">
+    <view class="nut-sidenavbar__content">
+      <view class="nut-sidenavbar__content__list" ref="list">
+        <slot></slot>
+      </view>
+    </view>
+  </view>
+</template>
+<script lang="ts">
+import { computed, onMounted, reactive, ref, toRefs, Ref } from 'vue';
+import { createComponent } from '../../utils/create';
+// import { createIntersectionObserver, IntersectionObserver } from '@tarojs/taro';
+const { componentName, create } = createComponent('sidenavbar');
+export default create({
+  props: {
+    offset: {
+      type: [String, Number],
+      default: 15
+    }
+  },
+  emits: [],
+  setup: (props: any, context: any) => {
+    const list = ref(null) as Ref;
+    const state = reactive({
+      count: 1
+      // observer: null as IntersectionObserver | null
+    });
+
+    const classes = computed(() => {
+      const prefixCls = componentName;
+      return {
+        [prefixCls]: true
+      };
+    });
+
+    const setPaddingLeft = (nodeList: any, level: number = 1) => {
+      for (let i = 0; i < nodeList.length; i++) {
+        let item = nodeList[i];
+        item.children[0].style.paddingLeft = props.offset * level + 'px';
+        if (!item.className.includes('nut-sidenavbaritem')) {
+          setPaddingLeft(Array.from(item.children[1].children), ++state.count);
+        }
+      }
+      state.count--;
+    };
+
+    const handleSlots = () => {
+      let childNodes = list.value.childNodes;
+      if (childNodes && childNodes.length) {
+        childNodes = Array.from(childNodes)
+          .filter((item: any) => item.nodeType !== 3)
+          .map((item: any) => {
+            return item;
+          });
+        setPaddingLeft(childNodes);
+      }
+    };
+
+    onMounted(() => {
+      handleSlots();
+      // state.observer = createIntersectionObserver(proxy, {
+      //   thresholds: [1],
+      //   initialRatio: 1,
+      //   observeAll: true
+      // });
+
+      // state.observer.observe(list.value, () => {
+      //   state.count = 1;
+      //   handleSlots();
+      // });
+    });
+
+    return {
+      ...toRefs(state),
+      list,
+      classes
+    };
+  }
+});
+</script>

+ 87 - 0
src/packages/__VUE/sidenavbar/index.vue

@@ -0,0 +1,87 @@
+<template>
+  <view :class="classes">
+    <view class="nut-sidenavbar__content">
+      <view class="nut-sidenavbar__content__list" ref="list">
+        <slot></slot>
+      </view>
+    </view>
+  </view>
+</template>
+<script lang="ts">
+import { computed, onMounted, reactive, ref, toRefs, Ref, watch } from 'vue';
+import { createComponent } from '../../utils/create';
+const { componentName, create } = createComponent('sidenavbar');
+export default create({
+  props: {
+    offset: {
+      type: [String, Number],
+      default: 15
+    }
+  },
+  emits: [],
+  setup: (props: any, context: any) => {
+    const list = ref(null) as Ref;
+    const state = reactive({
+      count: 1,
+      observer: null as MutationObserver | null
+    });
+
+    const classes = computed(() => {
+      const prefixCls = componentName;
+      return {
+        [prefixCls]: true
+      };
+    });
+
+    const setPaddingLeft = (nodeList: any, level: number = 1) => {
+      for (let i = 0; i < nodeList.length; i++) {
+        let item = nodeList[i];
+        item.children[0].style.paddingLeft = props.offset * level + 'px';
+        if (!item.className.includes('nut-sidenavbaritem')) {
+          setPaddingLeft(Array.from(item.children[1].children), ++state.count);
+        }
+      }
+      state.count--;
+    };
+
+    const handleSlots = () => {
+      let childNodes = list.value.childNodes;
+      if (childNodes.length) {
+        childNodes = Array.from(childNodes)
+          .filter((item: any) => item.nodeType !== 3)
+          .map((item: any) => {
+            return item;
+          });
+        setPaddingLeft(childNodes);
+      }
+    };
+
+    onMounted(() => {
+      handleSlots();
+      state.observer = new MutationObserver(function () {
+        state.count = 1;
+        handleSlots();
+      });
+
+      state.observer.observe(list.value, {
+        attributes: false,
+        childList: true,
+        characterData: false,
+        subtree: false
+      });
+    });
+
+    // watch(context.slots?.default(), () => {
+    //   console.log(123)
+    //   state.count = 1;
+    //   handleSlots();
+    // });
+
+    return {
+      ...toRefs(state),
+      list,
+      classes
+    };
+  }
+});
+</script>

+ 10 - 0
src/packages/__VUE/sidenavbaritem/index.scss

@@ -0,0 +1,10 @@
+.nut-sidenavbaritem {
+  height: 40px;
+  line-height: 40px;
+  display: block;
+  font-size: 16px;
+  &__title {
+    color: $title-color;
+    background-color: $sidenavbaritem-title-color;
+  }
+}

+ 42 - 0
src/packages/__VUE/sidenavbaritem/index.vue

@@ -0,0 +1,42 @@
+<template>
+  <view class="nut-sidenavbaritem" @click.stop="handleClick" :ikey="ikey">
+    <span class="nut-sidenavbaritem__title">
+      {{ title }}
+    </span>
+  </view>
+</template>
+<script lang="ts">
+import { computed } from 'vue';
+import { createComponent } from '../../utils/create';
+const { componentName, create } = createComponent('sidenavbaritem');
+export default create({
+  props: {
+    title: {
+      type: String,
+      default: ''
+    },
+    ikey: {
+      type: String,
+      default: ''
+    }
+  },
+  emits: ['click'],
+  setup: (props: any, context: any) => {
+    const classes = computed(() => {
+      const prefixCls = componentName;
+      return {
+        [prefixCls]: true
+      };
+    });
+
+    const handleClick = () => {
+      context.emit('click');
+    };
+
+    return {
+      classes,
+      handleClick
+    };
+  }
+});
+</script>

+ 39 - 0
src/packages/__VUE/subsidenavbar/index.scss

@@ -0,0 +1,39 @@
+.nut-subsidenavbar {
+  display: grid;
+  float: left;
+  width: 100%;
+  position: relative;
+  &__title {
+    display: block;
+    width: 100%;
+    height: 40px;
+    position: relative;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    box-sizing: border-box;
+    border-bottom: 1px solid $subsidenavbar-title-border-color;
+    color: $title-color;
+    font-size: $font-size-large;
+    background-color: $subsidenavbar-title-bg-color;
+    &__text {
+      line-height: 40px;
+      color: $title-color;
+    }
+    &__icon {
+      position: absolute;
+      top: 50%;
+      right: 20px;
+      transform: translateY(-50%);
+      i {
+        transition: transform 0.5s ease-in-out;
+        &.up {
+          transform: rotate(-180deg);
+        }
+      }
+    }
+  }
+  &__list {
+    width: 100%;
+  }
+}

+ 67 - 0
src/packages/__VUE/subsidenavbar/index.vue

@@ -0,0 +1,67 @@
+<template>
+  <view :class="classes" :ikey="ikey">
+    <view class="nut-subsidenavbar__title" @click.stop="handleClick">
+      <span class="nut-subsidenavbar__title__text">{{ title }}</span>
+      <span class="nut-subsidenavbar__title__icon"><nut-icon name="down-arrow" :class="direction"></nut-icon></span>
+    </view>
+    <view class="nut-subsidenavbar__list" :class="!direction ? 'nutFadeIn' : 'nutFadeOut'" :style="style">
+      <slot></slot>
+    </view>
+  </view>
+</template>
+<script lang="ts">
+import { computed, onMounted, reactive, toRefs } from 'vue';
+import { createComponent } from '../../utils/create';
+const { componentName, create } = createComponent('subsidenavbar');
+export default create({
+  props: {
+    title: {
+      type: String,
+      default: ''
+    },
+    ikey: {
+      type: [String, Number],
+      default: ''
+    },
+    open: {
+      type: Boolean,
+      default: true
+    }
+  },
+  emits: ['title-click'],
+  setup: (props: any, context: any) => {
+    const state = reactive({
+      direction: ''
+    });
+
+    const classes = computed(() => {
+      const prefixCls = componentName;
+      return {
+        [prefixCls]: true
+      };
+    });
+
+    const style = computed(() => {
+      return {
+        height: !state.direction ? 'auto' : '0px'
+      };
+    });
+
+    const handleClick = () => {
+      context.emit('title-click');
+      state.direction = !state.direction ? 'up' : '';
+    };
+
+    onMounted(() => {
+      state.direction = props.open ? '' : 'up';
+    });
+
+    return {
+      ...toRefs(state),
+      classes,
+      style,
+      handleClick
+    };
+  }
+});
+</script>

+ 10 - 0
src/packages/styles/variables.scss

@@ -452,5 +452,15 @@ $table-cols-padding: 10px;
 $table-tr-even-bg-color: #f3f3f3;
 $table-tr-odd-bg-color: $white;
 
+// sidenavbar
+$sidenavbar-content-bg-color: $white !default;
+
+// subsidenavbar
+$subsidenavbar-title-border-color: #f6f6f6 !default;
+$subsidenavbar-title-bg-color: #f6f6f6 !default;
+
+// sidenavbaritem
+$sidenavbaritem-title-color: $white !default;
+
 @import './mixins/index';
 @import './animation/index';

+ 2 - 1
src/sites/mobile-taro/vue/src/app.config.ts

@@ -52,7 +52,8 @@ export default {
         'pages/menu/index',
         'pages/pagination/index',
         'pages/indicator/index',
-        'pages/grid/index'
+        'pages/grid/index',
+        'pages/sidenavbar/index'
       ]
     },
     {

+ 3 - 0
src/sites/mobile-taro/vue/src/nav/pages/sidenavbar/index.config.ts

@@ -0,0 +1,3 @@
+export default {
+  navigationBarTitleText: 'SideNavBar'
+};

+ 104 - 0
src/sites/mobile-taro/vue/src/nav/pages/sidenavbar/index.vue

@@ -0,0 +1,104 @@
+<template>
+  <div class="demo">
+    <h2>基本用法</h2>
+    <nut-cell @click="handleClick1">
+      <span><label>右侧</label></span>
+    </nut-cell>
+    <nut-popup position="right" v-model:visible="show1" :style="{ width, height }">
+      <nut-sidenavbar>
+        <nut-subsidenavbar title="智能城市AI" ikey="6">
+          <nut-subsidenavbar title="人体识别1" ikey="9">
+            <nut-sidenavbaritem ikey="10" title="人体检测1"></nut-sidenavbaritem>
+            <nut-sidenavbaritem ikey="11" title="细粒度人像分割1"></nut-sidenavbaritem>
+          </nut-subsidenavbar>
+          <nut-subsidenavbar title="人体识别2" ikey="12">
+            <nut-sidenavbaritem ikey="13" title="人体检测2"></nut-sidenavbaritem>
+            <nut-sidenavbaritem ikey="14" title="细粒度人像分割2"></nut-sidenavbaritem>
+          </nut-subsidenavbar>
+        </nut-subsidenavbar>
+      </nut-sidenavbar>
+    </nut-popup>
+    <nut-cell @click="handleClick2">
+      <span><label>左侧</label></span>
+    </nut-cell>
+    <nut-popup position="left" v-model:visible="show2" :style="{ width, height }">
+      <nut-sidenavbar>
+        <nut-subsidenavbar title="图像理解" ikey="3" :open="false">
+          <nut-sidenavbaritem ikey="4" title="菜品识别"></nut-sidenavbaritem>
+          <nut-sidenavbaritem ikey="5" title="拍照购"></nut-sidenavbaritem>
+        </nut-subsidenavbar>
+        <nut-subsidenavbar title="自然语言处理" ikey="12">
+          <nut-sidenavbaritem ikey="13" title="词法分析"></nut-sidenavbaritem>
+          <nut-sidenavbaritem ikey="14" title="句法分析"></nut-sidenavbaritem>
+        </nut-subsidenavbar>
+      </nut-sidenavbar>
+    </nut-popup>
+    <h2>导航嵌套(建议最多三层),点击第一条回调</h2>
+    <div>
+      <nut-cell @click="handleClick3">
+        <span><label>显示</label></span>
+      </nut-cell>
+      <nut-popup position="right" v-model:visible="show3" :style="{ width, height }">
+        <nut-sidenavbar :show="show3">
+          <nut-sidenavbaritem ikey="1" title="人脸识别" @click="handleClick4('人脸识别')"></nut-sidenavbaritem>
+          <nut-sidenavbaritem ikey="2" title="云存自然语言处理"></nut-sidenavbaritem>
+          <nut-subsidenavbar title="图像理解" ikey="3" :open="false">
+            <nut-sidenavbaritem ikey="4" title="菜品识别"></nut-sidenavbaritem>
+            <nut-sidenavbaritem ikey="5" title="拍照购"></nut-sidenavbaritem>
+          </nut-subsidenavbar>
+          <nut-subsidenavbar title="智能城市AI" ikey="6">
+            <nut-sidenavbaritem ikey="7" title="企业风险预警模型"></nut-sidenavbaritem>
+            <nut-sidenavbaritem ikey="8" title="水质量检测"></nut-sidenavbaritem>
+            <nut-subsidenavbar title="人体识别" ikey="9">
+              <nut-sidenavbaritem ikey="10" title="人体检测"></nut-sidenavbaritem>
+              <nut-sidenavbaritem ikey="11" title="细粒度人像分割"></nut-sidenavbaritem>
+            </nut-subsidenavbar>
+          </nut-subsidenavbar>
+          <nut-subsidenavbar title="自然语言处理" ikey="12">
+            <nut-sidenavbaritem ikey="13" title="词法分析"></nut-sidenavbaritem>
+            <nut-sidenavbaritem ikey="14" title="句法分析"></nut-sidenavbaritem>
+          </nut-subsidenavbar>
+        </nut-sidenavbar>
+      </nut-popup>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, reactive, toRefs } from 'vue';
+export default defineComponent({
+  setup() {
+    const state = reactive({
+      show1: false,
+      show2: false,
+      show3: false,
+      width: '80%',
+      height: '100%'
+    });
+
+    const handleClick1 = () => {
+      state.show1 = true;
+    };
+
+    const handleClick2 = () => {
+      state.show2 = true;
+    };
+
+    const handleClick3 = () => {
+      state.show3 = true;
+    };
+
+    const handleClick4 = (msg: string) => {
+      console.log(msg);
+    };
+
+    return {
+      ...toRefs(state),
+      handleClick1,
+      handleClick2,
+      handleClick3,
+      handleClick4
+    };
+  }
+});
+</script>