提交 0cc86c79 编写于 作者: S suzigang

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

上级 b620bd15
......@@ -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"
}
]
},
......
<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>
# 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 | 导航点击 | - |
.nut-sidenavbar {
height: 100%;
overflow: auto;
display: block;
&__content {
position: relative;
background-color: $sidenavbar-content-bg-color;
display: block;
&__list {
width: 100%;
display: block;
}
}
}
<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>
<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>
.nut-sidenavbaritem {
height: 40px;
line-height: 40px;
display: block;
font-size: 16px;
&__title {
color: $title-color;
background-color: $sidenavbaritem-title-color;
}
}
<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>
.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%;
}
}
<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>
......@@ -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';
......@@ -52,7 +52,8 @@ export default {
'pages/menu/index',
'pages/pagination/index',
'pages/indicator/index',
'pages/grid/index'
'pages/grid/index',
'pages/sidenavbar/index'
]
},
{
......
export default {
navigationBarTitleText: 'SideNavBar'
};
<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>
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册