Browse Source

Merge branch 'next' into next_dev

yangxiaolu3 4 years ago
parent
commit
15fa9f03b0

+ 17 - 0
.github/workflows/sync-gitcode.yml

@@ -0,0 +1,17 @@
+name: Sync to Gitcode
+
+on:
+  push:
+    branches: [next]
+
+jobs:
+  deploy-site-sync-gitcode:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Sync to Gitcode
+        uses: wearerequired/git-mirror-action@master
+        env:
+          SSH_PRIVATE_KEY: ${{ secrets.GITEE_RSA_PRIVATE_KEY }}
+        with:
+          source-repo: git@github.com:jdf2e/nutui.git
+          destination-repo: git@gitcode.net:jd/nutui.git

+ 2 - 0
package.json

@@ -93,6 +93,8 @@
     "inquirer": "^8.2.0",
     "jest": "^26.6.3",
     "lint-staged": "^10.5.0",
+    "lzutf8": "^0.6.0",
+    "markdown-it-container": "^3.0.0",
     "prettier": "^2.0.0",
     "standard-version": "^9.3.0",
     "swiper": "6.5.1",

+ 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"
         }
       ]
     },

+ 67 - 30
src/packages/__VUE/avatar/doc.md

@@ -5,69 +5,106 @@
 用来代表用户或事物,支持图片、图标或字符展示。
 
 ### 安装
-``` javascript
+
+```javascript
 import { createApp } from 'vue';
 // vue
-import { Avatar,Icon } from '@nutui/nutui';
+import { Avatar, Icon } from '@nutui/nutui';
 // taro
-import { Avatar,Icon } from '@nutui/nutui-taro';
+import { Avatar, Icon } from '@nutui/nutui-taro';
 
 const app = createApp();
 app.use(Avatar);
 app.use(Icon);
 ```
 
-
 ### 基本用法
 
 内置 smal / normal / large 三种尺寸规格
 
-``` html
-<nut-avatar size="large" icon="https://img12.360buyimg.com/imagetools/jfs/t1/143702/31/16654/116794/5fc6f541Edebf8a57/4138097748889987.png"
-></nut-avatar>
-<nut-avatar size="normal" icon="https://img12.360buyimg.com/imagetools/jfs/t1/143702/31/16654/116794/5fc6f541Edebf8a57/4138097748889987.png"
-></nut-avatar>
-<nut-avatar size="small" icon="https://img12.360buyimg.com/imagetools/jfs/t1/143702/31/16654/116794/5fc6f541Edebf8a57/4138097748889987.png"
-></nut-avatar>  
+:::demo
+
+```html
+<template>
+  <nut-avatar
+    size="large"
+    icon="https://img12.360buyimg.com/imagetools/jfs/t1/143702/31/16654/116794/5fc6f541Edebf8a57/4138097748889987.png"
+  ></nut-avatar>
+  <nut-avatar
+    size="normal"
+    icon="https://img12.360buyimg.com/imagetools/jfs/t1/143702/31/16654/116794/5fc6f541Edebf8a57/4138097748889987.png"
+  ></nut-avatar>
+  <nut-avatar
+    size="small"
+    icon="https://img12.360buyimg.com/imagetools/jfs/t1/143702/31/16654/116794/5fc6f541Edebf8a57/4138097748889987.png"
+  ></nut-avatar>
+</template>
 ```
 
+:::
+
 ### 修改形状类型
 
-``` html
-<nut-avatar icon="my" shape="square"></nut-avatar>
-<nut-avatar icon="my" shape="round"></nut-avatar>
+:::demo
+
+```html
+<template>
+  <nut-avatar icon="my" shape="square"></nut-avatar>
+  <nut-avatar icon="my" shape="round"></nut-avatar>
+</template>
 ```
 
+:::
+
 ### 修改背景色
 
-``` html
-<nut-avatar icon="my" bg-color="#FA2C19" />
+:::demo
+
+```html
+<template>
+  <nut-avatar icon="my" bg-color="#FA2C19" />
+</template>
 ```
 
-### 修改背景icon
+:::
+
+### 修改背景 icon
+
+:::demo
 
-``` html
-<nut-avatar icon="https://img12.360buyimg.com/imagetools/jfs/t1/196430/38/8105/14329/60c806a4Ed506298a/e6de9fb7b8490f38.png" />
+```html
+<template>
+  <nut-avatar
+    icon="https://img12.360buyimg.com/imagetools/jfs/t1/196430/38/8105/14329/60c806a4Ed506298a/e6de9fb7b8490f38.png"
+  />
+</template>
 ```
 
+:::
+
 ### 设置头像的文本内容
 
-``` html
-<nut-avatar>N</nut-avatar>
+:::demo
+
+```html
+<template>
+  <nut-avatar>N</nut-avatar>
+</template>
 ```
 
+:::
 
 ### Prop
 
-| 字段     | 说明                                                                     | 类型   | 默认值 |
-|----------|--------------------------------------------------------------------------|--------|--------|
-| bg-color | 设置头像背景色                                                           | String | #eee   |
-| size     | 设置头像的大小,提供三种:large/normal/small,支持直接输入数字           | String | normal |
-| shape    | 设置头像的形状,默认是圆形,可以设置为square方形                         | String | round  |
-| icon     | 设置头像的icon图标, 类似Icon组件的name属性,支持名称和链接 | String | ''     |
+| 字段     | 说明                                                             | 类型   | 默认值 |
+| -------- | ---------------------------------------------------------------- | ------ | ------ |
+| bg-color | 设置头像背景色                                                   | String | #eee   |
+| size     | 设置头像的大小,提供三种:large/normal/small,支持直接输入数字   | String | normal |
+| shape    | 设置头像的形状,默认是圆形,可以设置为 square 方形               | String | round  |
+| icon     | 设置头像的 icon 图标, 类似 Icon 组件的 name 属性,支持名称和链接 | String | ''     |
 
 ### Events
 
-| 字段     | 说明                 | 类型     | 回调参数 |
-|----------|----------------------|----------|----------|
-| active-avatarror | 点击触发事件 | Function | event    |
+| 字段             | 说明         | 类型     | 回调参数 |
+| ---------------- | ------------ | -------- | -------- |
+| active-avatarror | 点击触发事件 | Function | event    |

+ 112 - 56
src/packages/__VUE/button/doc.md

@@ -6,7 +6,7 @@
 
 ### 安装
 
-``` javascript
+```javascript
 import { createApp } from 'vue';
 // vue
 import { Button } from '@nutui/nutui';
@@ -23,121 +23,177 @@ app.use(Button);
 
 按钮支持 `default`、`primary`、`info`、`warning`、`danger`、`success` 六种类型,默认为 `default`。
 
+:::demo
+
 ```html
-<nut-button type="primary">主要按钮</nut-button>
-<nut-button type="info">信息按钮</nut-button>
-<nut-button type="default">默认按钮</nut-button>
-<nut-button type="danger">危险按钮</nut-button>
-<nut-button type="warning">警告按钮</nut-button>
-<nut-button type="success">成功按钮</nut-button>
+<template>
+  <nut-button type="primary">主要按钮</nut-button>
+  <nut-button type="info">信息按钮</nut-button>
+  <nut-button type="default">默认按钮</nut-button>
+  <nut-button type="danger">危险按钮</nut-button>
+  <nut-button type="warning">警告按钮</nut-button>
+  <nut-button type="success">成功按钮</nut-button>
+</template>
 ```
 
+:::
+
 ### 朴素按钮
 
 通过 `plain` 属性将按钮设置为朴素按钮,朴素按钮的文字为按钮颜色,背景为白色。
 
+:::demo
+
 ```html
-<nut-button plain type="primary">朴素按钮</nut-button>
-<nut-button plain type="info">朴素按钮</nut-button>
+<template>
+  <nut-button plain type="primary">朴素按钮</nut-button>
+  <nut-button plain type="info">朴素按钮</nut-button>
+</template>
 ```
 
+:::
+
 ### 禁用状态
 
 通过 `disabled` 属性来禁用按钮,禁用状态下按钮不可点击。
 
+:::demo
+
 ```html
-<nut-button disabled type="primary">禁用状态</nut-button>
-<nut-button plain disabled type="info">禁用状态</nut-button>
-<nut-button plain disabled type="primary">禁用状态</nut-button>
+<template>
+  <nut-button disabled type="primary">禁用状态</nut-button>
+  <nut-button plain disabled type="info">禁用状态</nut-button>
+  <nut-button plain disabled type="primary">禁用状态</nut-button>
+</template>
 ```
 
+:::
+
 ### 按钮形状
 
 通过 `shape` 属性设置按钮形状,支持圆形、方形按钮,默认为圆形。
 
+:::demo
+
 ```html
-<nut-button shape="square" type="primary">方形按钮</nut-button>
-<nut-button type="info">圆形按钮</nut-button>
+<template>
+  <nut-button shape="square" type="primary">方形按钮</nut-button>
+  <nut-button type="info">圆形按钮</nut-button>
+</template>
 ```
 
+:::
+
 ### 加载状态
 
-```html
-<nut-button loading type="info"></nut-button>
-<nut-button loading type="warning">加载中...</nut-button>
-<nut-button :loading="isLoading" type="success" @click="changeLoading">Click me!</nut-button>
-```
-``` javascript
-  // ...
-  let isLoading = ref(false);
-  const changeLoading = () => {
-    isLoading.value = true;
-    setTimeout(() => {
-      isLoading.value = false;
-    }, 3000);
-  };
+:::demo
 
-  return {
-    isLoading,
-    changeLoading
+```html
+<template>
+  <nut-button loading type="info"></nut-button>
+  <nut-button loading type="warning">加载中...</nut-button>
+  <nut-button :loading="isLoading" type="success" @click="changeLoading">Click me!</nut-button>
+</template>
+
+<script>
+  import { ref } from 'vue';
+  export default {
+    setup(props) {
+      let isLoading = ref(false);
+      const changeLoading = () => {
+        isLoading.value = true;
+        setTimeout(() => {
+          isLoading.value = false;
+        }, 3000);
+      };
+      return {
+        isLoading,
+        changeLoading
+      };
+    }
   };
-  // ...
+</script>
 ```
 
+:::
+
 ### 图标按钮
 
+:::demo
+
 ```html
+<template>
   <nut-button shape="square" plain type="primary" icon="star-fill"></nut-button>
   <nut-button shape="square" type="primary" icon="star">收藏</nut-button>
+</template>
 ```
 
+:::
+
 ### 按钮尺寸
 
 支持 `large`、`normal`、`small`、`mini` 四种尺寸,默认为 `normal`。
 
+:::demo
+
 ```html
-<nut-button size="large" type="primary">大号按钮</nut-button>
-<nut-button type="primary">普通按钮</nut-button>
-<nut-button size="small" type="primary">小型按钮</nut-button>
-<nut-button size="mini" type="primary">小型按钮</nut-button>
+<template>
+  <nut-button size="large" type="primary">大号按钮</nut-button>
+  <nut-button type="primary">普通按钮</nut-button>
+  <nut-button size="small" type="primary">小型按钮</nut-button>
+  <nut-button size="mini" type="primary">小型按钮</nut-button>
+</template>
 ```
 
+:::
+
 ### 块级元素
 
 按钮在默认情况下为行内块级元素,通过 `block` 属性可以将按钮的元素类型设置为块级元素,常用来实现通栏按钮。
 
+:::demo
+
 ```html
-<nut-button block type="primary">块级元素</nut-button>
+<template>
+  <nut-button block type="primary">块级元素</nut-button>
+</template>
 ```
 
+:::
+
 ### 自定义颜色
+
 通过 color 属性可以自定义按钮的颜色。
+:::demo
+
 ```html
-<nut-button color="#7232dd">单色按钮</nut-button>
-<nut-button color="#7232dd" plain>单色按钮</nut-button>
-<nut-button color="linear-gradient(to right, #ff6034, #ee0a24)">
-  渐变色按钮
-</nut-button>
+<template>
+  <nut-button color="#7232dd">单色按钮</nut-button>
+  <nut-button color="#7232dd" plain>单色按钮</nut-button>
+  <nut-button color="linear-gradient(to right, #ff6034, #ee0a24)"> 渐变色按钮 </nut-button>
+</template>
 ```
+
+:::
+
 ## API
 
 ### Props
 
-| 参数         | 说明                             | 类型   | 默认值           |
-|--------------|----------------------------------|--------|------------------|
-| type         | 类型,可选值为 `primary` `info` `warning` `danger` `success` | String |`default`         |
-| size        | 尺寸,可选值为 `large` `small` `mini`  | String | `normal`      |
-| shape         | 形状,可选值为 `square` | String | `round`             |
-| color | 按钮颜色,支持传入 linear-gradient 渐变色     | String | - |
-| plain          | 	是否为朴素按钮                       | Boolean | `false`             |
-| disabled          | 	是否禁用按钮                       | Boolean | `false`              |
-| block          | 是否为块级元素                        | Boolean | `false`               |
-| icon          | 按钮图标,同Icon组件name属性                        | String | -     |
-| loading          | 按钮loading状态                        | Boolean | `false`               |
+| 参数     | 说明                                                         | 类型    | 默认值    |
+| -------- | ------------------------------------------------------------ | ------- | --------- |
+| type     | 类型,可选值为 `primary` `info` `warning` `danger` `success` | String  | `default` |
+| size     | 尺寸,可选值为 `large` `small` `mini`                        | String  | `normal`  |
+| shape    | 形状,可选值为 `square`                                      | String  | `round`   |
+| color    | 按钮颜色,支持传入 linear-gradient 渐变色                    | String  | -         |
+| plain    | 是否为朴素按钮                                               | Boolean | `false`   |
+| disabled | 是否禁用按钮                                                 | Boolean | `false`   |
+| block    | 是否为块级元素                                               | Boolean | `false`   |
+| icon     | 按钮图标,同 Icon 组件 name 属性                             | String  | -         |
+| loading  | 按钮 loading 状态                                            | Boolean | `false`   |
 
 ### Events
 
-| 事件名 | 说明           | 回调参数     |
-|--------|----------------|--------------|
+| 事件名 | 说明           | 回调参数          |
+| ------ | -------------- | ----------------- |
 | click  | 点击按钮时触发 | event: MouseEvent |
-

+ 124 - 69
src/packages/__VUE/cell/doc.md

@@ -6,133 +6,188 @@
 
 ### 安装
 
-``` javascript
+```javascript
 import { createApp } from 'vue';
-import { Cell,Icon } from '@nutui/nutui';
+import { Cell, Icon } from '@nutui/nutui';
 
 const app = createApp();
 app.use(Cell).use(Icon);
 ```
 
-
 ### 基本用法
 
-``` html
-<nut-cell title="我是标题" desc="描述文字"></nut-cell>
-<nut-cell title="我是标题" sub-title="副标题描述" desc="描述文字"></nut-cell>
-<nut-cell title="点击测试" @click="testClick"></nut-cell>
-<nut-cell title="圆角设置 0" round-radius="0"></nut-cell>
-```
-
-``` javascript
-// ...
-import { ref } from 'vue';
-import { Toast } from '@nutui/nutui';
-export default {
-  setup() {
+:::demo
+
+```html
+<template>
+  <nut-cell title="我是标题" desc="描述文字"></nut-cell>
+  <nut-cell title="我是标题" sub-title="副标题描述" desc="描述文字"></nut-cell>
+  <nut-cell title="点击测试" @click="testClick"></nut-cell>
+  <nut-cell title="圆角设置 0" round-radius="0"></nut-cell>
+</template>
+<script>
+  import { ref } from 'vue';
+  import { Toast } from '@nutui/nutui';
+  export default {
+    setup() {
       const switchChecked = ref(true);
       const testClick = (event) => {
-        Toast.text('点击事件')
+        Toast.text('点击事件');
       };
-      return { testClick,switchChecked };
-  }
-}
-// ...
+      return { testClick, switchChecked };
+    }
+  };
+</script>
 ```
 
+:::
+
 ### 直接使用插槽
 
-``` html
- <nut-cell title="我是标题" desc="描述文字">
-  <div>自定义内容</div>
- </nut-cell>  
+:::demo
+
+```html
+<template>
+  <nut-cell title="我是标题" desc="描述文字">
+    <div>自定义内容</div>
+  </nut-cell>
+</template>
 ```
 
+:::
+
 ### 链接 | 分组用法
 
-``` html
-<nut-cell-group title="链接 | 分组用法" desc="使用 nut-cell-group 支持 title desc slots">
-  <nut-cell title="链接" is-link></nut-cell>
-  <nut-cell title="URL 跳转" desc="https://jd.com" is-link url="https://jd.com"></nut-cell>
-  <nut-cell title="路由跳转 ’/‘ " to="/"></nut-cell>
-</nut-cell-group>
+:::demo
+
+```html
+<template>
+  <nut-cell-group title="链接 | 分组用法" desc="使用 nut-cell-group 支持 title desc slots">
+    <nut-cell title="链接" is-link></nut-cell>
+    <nut-cell title="URL 跳转" desc="https://m.jd.com" is-link url="https://m.jd.com"></nut-cell>
+    <nut-cell title="路由跳转 ’/‘ " to="/"></nut-cell>
+  </nut-cell-group>
+</template>
 ```
 
+:::
+
 ### 自定义右侧箭头区域
 
-``` html
-<nut-cell-group title="自定义右侧箭头区域">
-  <nut-cell title="Switch">
-    <template v-slot:link>
-      <nut-switch v-model="switchChecked" />
-    </template>
-  </nut-cell>
-</nut-cell-group>
+:::demo
+
+```html
+<template>
+  <nut-cell-group title="自定义右侧箭头区域">
+    <nut-cell title="Switch">
+      <template v-slot:link>
+        <nut-switch v-model="switchChecked" />
+      </template>
+    </nut-cell>
+  </nut-cell-group>
+</template>
+<script lang="ts">
+  import { ref } from 'vue';
+  export default {
+    setup() {
+      const testClick = (event: Event) => {
+        console.log('点击事件');
+      };
+      const switchChecked = ref(true);
+      return { testClick, switchChecked };
+    }
+  };
+</script>
 ```
+
+:::
+
 ### 自定义左侧 Icon 区域
 
-``` html
-<nut-cell-group title="自定义左侧 Icon 区域">
-  <nut-cell title="图片">
-    <template v-slot:icon>
-      <img class="nut-icon" src="https://img11.360buyimg.com/imagetools/jfs/t1/137646/13/7132/1648/5f4c748bE43da8ddd/a3f06d51dcae7b60.png" />
-    </template>
-  </nut-cell>
-</nut-cell-group>
+:::demo
+
+```html
+<template>
+  <nut-cell-group title="自定义左侧 Icon 区域">
+    <nut-cell title="图片">
+      <template v-slot:icon>
+        <img
+          class="nut-icon"
+          src="https://img11.360buyimg.com/imagetools/jfs/t1/137646/13/7132/1648/5f4c748bE43da8ddd/a3f06d51dcae7b60.png"
+        />
+      </template>
+    </nut-cell>
+  </nut-cell-group>
+</template>
 ```
 
+:::
+
 ### 单元格展示图标
 
-``` html
-<nut-cell title="姓名" icon="my" desc="张三"></nut-cell>
+:::demo
+
+```html
+<template>
+  <nut-cell title="姓名" icon="my" desc="张三"></nut-cell>
+</template>
 ```
+
+:::
+
 ### 只展示 desc ,可通过 desc-text-align 调整内容位置
 
-``` html
-<nut-cell desc-text-align="left" desc="张三"></nut-cell>
+:::demo
+
+```html
+<template>
+  <nut-cell desc-text-align="left" desc="张三"></nut-cell>
+</template>
 ```
 
+:::
+
 ## API
 
 ### CellGroup Prop
 
 | 字段  | 说明     | 类型   | 默认值 |
-|-------|----------|--------|--------|
+| ----- | -------- | ------ | ------ |
 | title | 分组标题 | String | -      |
 | desc  | 分组描述 | String | -      |
 
 ### Cell Prop
 
-| 字段                   | 说明                                                                                           | 类型           | 默认值 |
-|------------------------|------------------------------------------------------------------------------------------------|----------------|--------|
-| title                  | 标题名称                                                                                       | String         | -      |
-| sub-title              | 左侧副标题                                                                                     | String         | -      |
-| desc                   | 右侧描述                                                                                       | String         | -      |
-| desc-text-align        | 右侧描述文本对齐方式 [text-align](https://www.w3school.com.cn/cssref/pr_text_text-align.asp)   | String         | right  |
-| is-link                | 是否展示右侧箭头并开启点击反馈                                                                 | Boolean        | false  |
-| icon                   | 左侧 [图标名称](#/icon) 或图片链接                                                             | String         | -      |
-| round-radius           | 圆角半径                                                                                       | Number         | 6px    |
-| url `小程序不支持`     | 点击后跳转的链接地址                                                                           | String         | -      |
-| to   `小程序不支持`    | 点击后跳转的目标路由对象,同 vue-router 的 [to 属性](https://router.vuejs.org/zh/api/#to) 属性 | String|Object | -      |
-| replace `小程序不支持` | 是否在跳转时替换当前页面历史                                                                   | Boolean        | false  |
+| 字段                   | 说明                                                                                           | 类型             | 默认值 |
+| ---------------------- | ---------------------------------------------------------------------------------------------- | ---------------- | ------ |
+| title                  | 标题名称                                                                                       | String           | -      |
+| sub-title              | 左侧副标题                                                                                     | String           | -      |
+| desc                   | 右侧描述                                                                                       | String           | -      |
+| desc-text-align        | 右侧描述文本对齐方式 [text-align](https://www.w3school.com.cn/cssref/pr_text_text-align.asp)   | String           | right  |
+| is-link                | 是否展示右侧箭头并开启点击反馈                                                                 | Boolean          | false  |
+| icon                   | 左侧 [图标名称](#/icon) 或图片链接                                                             | String           | -      |
+| round-radius           | 圆角半径                                                                                       | Number           | 6px    |
+| url `小程序不支持`     | 点击后跳转的链接地址                                                                           | String           | -      |
+| to `小程序不支持`      | 点击后跳转的目标路由对象,同 vue-router 的 [to 属性](https://router.vuejs.org/zh/api/#to) 属性 | String  Object | -      |
+| replace `小程序不支持` | 是否在跳转时替换当前页面历史                                                                   | Boolean          | false  |
 
 ### Cell Event
 
 | 名称  | 说明     | 回调参数    |
-|-------|----------|-------------|
+| ----- | -------- | ----------- |
 | click | 点击事件 | event:Event |
 
-
 ## Cell Slots
 
 | 名称          | 说明                 |
-|---------------|----------------------|
+| ------------- | -------------------- |
 | icon `v3.1.4` | 自定义左侧`icon`区域 |
 | default       | 自定义内容           |
 | link          | 自定义右侧`link`区域 |
 
 ## CellGroup Slots
-| 名称            | 说明                |
-|-----------------|---------------------|
+
+| 名称            | 说明                  |
+| --------------- | --------------------- |
 | title `v3.1.10` | 自定义`title`标题区域 |
 | desc `v3.1.12`  | 自定义`desc`描述区域  |

+ 9 - 3
src/packages/__VUE/popup/index.taro.vue

@@ -15,7 +15,7 @@
       <view v-show="visible" :class="classes" :style="popStyle" @click="onClick">
         <slot v-if="showSlot"></slot>
         <view
-          v-if="closeable"
+          v-if="closed"
           @click="onClickCloseIcon"
           class="nutui-popup__close-icon"
           :class="'nutui-popup__close-icon--' + closeIconPosition"
@@ -102,13 +102,13 @@ export default create({
   },
   emits: ['click', 'click-close-icon', 'open', 'close', 'opend', 'closed', 'update:visible', 'click-overlay'],
   setup(props, { emit }) {
-    const { proxy } = getCurrentInstance() as any;
     const state = reactive({
       zIndex: props.zIndex ? (props.zIndex as number) : _zIndex,
       showSlot: true,
       transitionName: `popup-fade-${props.position}`,
       overLayCount: 1,
-      keepAlive: false
+      keepAlive: false,
+      closed: props.closeable
     });
     const [lockScroll, unlockScroll] = useLockScroll(() => props.lockScroll);
     const classes = computed(() => {
@@ -214,6 +214,12 @@ export default create({
         value === 'center' ? (state.transitionName = 'popup-fade') : (state.transitionName = `popup-slide-${value}`);
       }
     );
+    watch(
+      () => props.closeable,
+      (value) => {
+        state.closed = value;
+      }
+    );
     return {
       ...toRefs(state),
       popStyle,

+ 11 - 3
src/packages/__VUE/popup/index.vue

@@ -15,7 +15,7 @@
       <view v-show="visible" :class="classes" :style="popStyle" @click="onClick">
         <slot v-if="showSlot"></slot>
         <view
-          v-if="closeable"
+          v-if="closed"
           @click="onClickCloseIcon"
           class="nutui-popup__close-icon"
           :class="'nutui-popup__close-icon--' + closeIconPosition"
@@ -41,7 +41,7 @@
       <view v-show="visible" :class="classes" :style="popStyle" @click="onClick">
         <slot v-if="showSlot"></slot>
         <view
-          v-if="closeable"
+          v-if="closed"
           @click="onClickCloseIcon"
           class="nutui-popup__close-icon"
           :class="'nutui-popup__close-icon--' + closeIconPosition"
@@ -149,7 +149,8 @@ export default create({
       showSlot: true,
       transitionName: `popup-fade-${props.position}`,
       overLayCount: 1,
-      keepAlive: false
+      keepAlive: false,
+      closed: props.closeable
     });
 
     const [lockScroll, unlockScroll] = useLockScroll(() => props.lockScroll);
@@ -274,6 +275,13 @@ export default create({
       }
     );
 
+    watch(
+      () => props.closeable,
+      (value) => {
+        state.closed = value;
+      }
+    );
+
     return {
       ...toRefs(state),
       popStyle,

+ 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>

+ 4 - 4
src/packages/__VUE/switch/doc.md

@@ -115,14 +115,14 @@ export default {
 | disable        | 禁用状态         | Boolean | `false`               |
 | loading        | 加载状态         | Boolean | `false`               |
 | name        | [图标名称](#/icon)         | String | `loading`               |
-| color        | [图标颜色](#/icon)          | String | -               |
-| size        | [图标尺寸](#/icon)          | String、Number | `12px`               |
+| color        | [图标颜色](#/icon),仅在`loading`状态下生效          | String | -               |
+| size        | [图标尺寸](#/icon),仅在`loading`状态下生效          | String、Number | `12px`               |
 | active-color   | 打开时的背景颜色 | String  | `#fa2c19`    |
 | inactive-color | 关闭时的背景颜色 | String  | `#ebebeb` |
 | active-text    | 打开时文字描述   | String  | -                     |
 | inactive-text  | 关闭时文字描述   | String  | -                     |
-| active-value  | 打开时组件的值   | Boolean、String、Number  | true                     |
-| inactive-value  | 关闭组件的值   | Boolean、String、Number  | false                     |
+| active-value  | 打开时组件的值   | Boolean、String、Number  | `true`  |
+| inactive-value  | 关闭组件的值   | Boolean、String、Number  | `false`  |
 
 
 ### Events

+ 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';

+ 30 - 0
src/sites/doc/components/demo-block/basedUtil.ts

@@ -0,0 +1,30 @@
+import * as LZUTF8 from 'lzutf8';
+function encodeBase64(str: Uint8Array): any {
+  // @ts-ignore
+  return LZUTF8.encodeBase64(str);
+}
+function decodeBase64(str: string): any {
+  // @ts-ignore
+  return LZUTF8.decodeBase64(str);
+}
+
+export function compressText(str: string): any {
+  // @ts-ignore
+  return encodeBase64(LZUTF8.compress(str, { outputEncoding: 'ByteArray' }));
+}
+export function decompressText(str: string): any {
+  // @ts-ignore
+  return LZUTF8.decompress(decodeBase64(str));
+}
+
+export function copyCodeHtml(code: string, callback: any): void {
+  const oInput = document.createElement('input');
+  oInput.value = code;
+  document.body.appendChild(oInput);
+  oInput.select(); // 选择对象
+  document.execCommand('Copy'); // 执行浏览器复制命令
+  oInput.className = 'oInput';
+  oInput.style.display = 'none';
+  oInput.setSelectionRange(0, 9999);
+  callback();
+}

+ 47 - 0
src/sites/doc/components/demo-block/demoBlock.scss

@@ -0,0 +1,47 @@
+.online-part {
+  display: flex;
+  justify-content: center;
+  margin-top: 0px;
+  border: 1px solid #eee;
+  border-top: 0px;
+  width: 100%;
+  height: 35px;
+  .list {
+    list-style-type: none;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    flex: 0.5;
+    border-top: 1px dashed #eee;
+    border-right: 1px solid #eee;
+    position: relative;
+    &:last-child {
+      border-right: 0px;
+      border-left: 0px;
+    }
+    &:hover {
+      background-color: #eee;
+      cursor: pointer;
+      & .online-tips {
+        display: block;
+      }
+    }
+    .online-icon {
+      width: 20px;
+      height: 20px;
+    }
+    .online-tips {
+      display: none;
+      position: absolute;
+      top: 0px;
+      left: 53%;
+      padding: 2px 6px;
+      background-color: #333;
+      color: #fff;
+      font-size: 12px;
+      border-top-right-radius: 10px;
+      border-bottom-right-radius: 10px;
+      border-top-left-radius: 10px;
+    }
+  }
+}

+ 64 - 22
src/sites/doc/components/demo-block/demoBlock.vue

@@ -1,33 +1,75 @@
 <template>
-  <div>
-    <slot name="highlight"></slot>
+  <div class="online-code" ref="onlineCode">
     <slot></slot>
-    <p class="online-part">
-      <a href="//gitpod.io/#https://github.com/jdf2e/nutui.git" target="_blank" class="online-btn">在线运行</a>
-    </p>
+    <div class="online-part">
+      <a class="list" :href="jumpHref" target="_blank">
+        <img
+          class="online-icon"
+          src="https://img12.360buyimg.com/imagetools/jfs/t1/214225/34/8715/7002/61c31bf1E69324ee9/7a452063eba88be4.png"
+        />
+        <div class="online-tips">在线调试</div>
+      </a>
+      <div class="list" @click="copyCode">
+        <img
+          class="online-icon"
+          src="https://img10.360buyimg.com/imagetools/jfs/t1/142615/10/25537/3671/61c31e6eE3ba7fb90/d1953e2b47e40e86.png"
+        />
+        <div class="online-tips">复制代码</div>
+      </div>
+    </div>
   </div>
 </template>
 <script>
+import { ref, getCurrentInstance, onMounted, computed } from 'vue';
+import { compressText, copyCodeHtml, decompressText } from './basedUtil';
 export default {
-  setup() {
-    function onlineFun() {
-      alert('hello');
-    }
+  setup(props, ctx) {
+    const sourceMainReactJsStr = `//import VConsole from "vconsole";
+//var vConsole = new VConsole();
+import React from "react";
+import ReactDOM from "react-dom";
+import '@nutui/nutui-react/dist/style.css'
+import App from "./app.jsx";
+import "./app.scss";
+ReactDOM.render(
+  <App/>,
+  document.getElementById("app")
+);`;
+    const sourceMainJsStr = `//import VConsole from "vconsole";
+//var vConsole = new VConsole();
+import { createApp } from "vue";
+import App from "./app.vue";
+import NutUI from "@nutui/nutui";
+import "./app.scss";
+import "@nutui/nutui/dist/style.css";
+createApp(App).use(NutUI).mount("#app");`;
+
+    const onlineCode = ref(null);
+    const sourceMainJs = compressText(sourceMainJsStr);
+    const mainJs = ref(sourceMainJs);
+
+    const sourceMainReactJs = compressText(sourceMainReactJsStr);
+    const mainReactJs = ref(sourceMainReactJs);
+
+    const jumpHref = ref(``);
+    onMounted(() => {
+      if (onlineCode.value.dataset.type === 'react') {
+        jumpHref.value = `https://codehouse.jd.com/?source=share&type=react&mainJs=${mainReactJs.value}&appValue=${onlineCode.value.dataset.value}&scssValue=`;
+      } else {
+        jumpHref.value = `https://codehouse.jd.com/?source=share&type=vue&mainJs=${mainJs.value}&appValue=${onlineCode.value.dataset.value}&scssValue=`;
+      }
+    });
+    const copyCode = () => {
+      const sourceValue = decompressText(onlineCode.value.dataset.value);
+      copyCodeHtml(sourceValue, () => {
+        alert('复制成功');
+      });
+    };
     return {
-      onlineFun
+      jumpHref,
+      onlineCode,
+      copyCode
     };
   }
 };
 </script>
-<style lang="scss" scoped>
-.online-part {
-  display: flex;
-  justify-content: flex-end;
-  .online-btn {
-    display: block;
-    padding: 10px;
-    background: #eee;
-    cursor: pointer;
-  }
-}
-</style>

+ 1 - 0
src/sites/doc/components/demo-block/index.ts

@@ -1,2 +1,3 @@
 import DemoBlock from './demoBlock.vue';
+import './demoBlock.scss';
 export default DemoBlock;

+ 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>

+ 20 - 0
vite.config.ts

@@ -5,6 +5,7 @@ import Markdown from 'vite-plugin-md';
 import path from 'path';
 import config from './package.json';
 const hljs = require('highlight.js'); // https://highlightjs.org/
+import { compressText } from './src/sites/doc/components/demo-block/basedUtil';
 const resolve = path.resolve;
 // https://vitejs.dev/config/
 export default defineConfig({
@@ -56,6 +57,25 @@ export default defineConfig({
 
           return ''; // 使用额外的默认转义
         }
+      },
+      markdownItSetup(md) {
+        md.use(require('markdown-it-container'), 'demo', {
+          validate: function (params) {
+            return params.match(/^demo\s*(.*)$/);
+          },
+
+          render: function (tokens, idx) {
+            const m = tokens[idx].info.trim().match(/^demo\s*(.*)$/);
+            if (tokens[idx].nesting === 1) {
+              // opening tag
+              const contentHtml = compressText(tokens[idx + 1].content);
+              return `<demo-block data-type="vue" data-value="${contentHtml}">` + md.utils.escapeHtml(m[1]) + '\n';
+            } else {
+              // closing tag
+              return '</demo-block>\n';
+            }
+          }
+        });
       }
     })
     // legacy({