提交 bea93ea9 编写于 作者: A ailululu

Merge branch 'next' of https://github.com/jdf2e/nutui into next

......@@ -71,12 +71,40 @@ export const nav = [
show: true,
desc: '按钮用于触发一个操作,如提交表单。',
author: 'richard1015'
},
{
name: 'collapse',
sort: 2,
cName: '折叠面板',
type: 'component',
show: true,
desc: '折叠面板',
author: 'Ymm0008'
},
{
name: 'collapse',
sort: 3,
cName: '折叠面板-item',
type: 'component',
show: false,
desc: '折叠面板-item',
author: 'Ymm0008'
}
]
},
{
name: '操作反馈',
packages: []
packages: [
{
name: 'BackTop',
sort: '1',
cName: '回到顶部',
type: 'component',
show: true,
desc: '较长页面快捷回到顶部',
author: 'liqiong43'
}
]
},
{
name: '基础组件',
......@@ -125,6 +153,15 @@ export const nav = [
show: true,
desc: '价格组件',
author: 'ailululu'
},
{
name: 'Checkbox',
sort: 5,
cName: '复选按钮',
type: 'component',
show: true,
desc: '复选按钮',
author: 'Ymm0008'
}
]
},
......
<template>
<div class="demo-list">
<h4>基本用法</h4>
<div class="show-demo">
<nut-collapse v-model:active="active1">
<nut-collapse-item :title="title1" :name="1">
京东“厂直优品计划”首推“政府优品馆” 3年覆盖80%镇级政府
</nut-collapse-item>
<nut-collapse-item :title="title2" :name="2">
京东到家:教师节期间 创意花束销量增长53倍
</nut-collapse-item>
<nut-collapse-item :title="title3" :name="3" disabled> </nut-collapse-item>
</nut-collapse>
</div>
<div class="show-demo">
<h4>手风琴</h4>
<nut-collapse v-model:active="active2" :accordion="true">
<nut-collapse-item :title="title1" :name="1">
华为终端操作系统EMUI 11发布,9月11日正式开启
</nut-collapse-item>
<nut-collapse-item :title="title2" :name="2" :subTitle="subTitle">
中国服务机器人市场已占全球市场超1/4
</nut-collapse-item>
<nut-collapse-item :title="title3" :name="3">
QuestMobile:90后互联网用户规模超越80后达3.62亿
</nut-collapse-item>
</nut-collapse>
</div>
<div class="show-demo">
<h4>图标展示</h4>
<nut-collapse
v-model:active="active3"
:accordion="true"
:expandIconPosition="expandIconPosition"
:icon="icon"
:iconWidth="iconWidth"
:iconHeight="iconHeight"
:rotate="rotate"
>
<nut-collapse-item :title="title1" :name="1">
京东数科IPO将引入“绿鞋机制”
</nut-collapse-item>
<nut-collapse-item :title="title2" :name="2">
世界制造业大会开幕,阿里巴巴与安徽合作再升级
</nut-collapse-item>
</nut-collapse>
</div>
</div>
</template>
<script lang="ts">
import { reactive, toRefs } from 'vue';
import { createComponent } from '@/utils/create';
const { createDemo } = createComponent('collapse');
export default createDemo({
setup(props, context) {
const data = reactive({
active1: [1, '2'],
active2: 1,
active3: 1,
expandIconPosition: 'left',
title1: '标题1',
title2: '标题2',
title3: '标题3',
subTitle: '副标题',
icon: 'https://img11.360buyimg.com/imagetools/jfs/t1/132849/8/9709/550/5f5f0d8aE802abee7/68bd02b3a52c3988.png',
iconWidth: '20px',
iconHeight: '20px',
rotate: 90
});
const change = (name: string) => {
console.log(`点击了name是${name}的面板`);
};
return {
change,
...toRefs(data)
};
}
});
</script>
<style>
#app {
background: #f7f8fa;
}
</style>
<style lang="scss" scoped>
.demo-list {
margin: 60px 0;
h4 {
margin: 20px 0 10px 25px;
font-size: 14px;
color: #909ca4;
}
}
</style>
# Collapse 折叠面板
## 基本用法
通过`v-model`控制展开的面板列表,`activeNames`为数组格式
```html
<nut-collapse v-model="activeNames">
<nut-collapse-item title="标题1" :name="1">
京东“厂直优品计划”首推“政府优品馆” 3年覆盖80%镇级政府
</nut-collapse-item>
<nut-collapse-item title="标题2" :name="2">
京东到家:教师节期间 创意花束销量增长53倍
</nut-collapse-item>
<nut-collapse-item title="标题3" :name="3" disabled>
</nut-collapse-item>
</nut-collapse>
```
``` javascript
export default {
data() {
return {
activeNames: [1, 2]
};
}
};
```
### 手风琴
通过`accordion`可以设置为手风琴模式,最多展开一个面板,此时`activeName`为字符串格式;`subTitle`可以设置副标题的内容
```html
<nut-collapse v-model="activeName" :accordion="true">
<nut-collapse-item :title="title1" :name="1">
华为终端操作系统EMUI 11发布,9月11日正式开启
</nut-collapse-item>
<nut-collapse-item :title="title2" :name="2" :sub-title="subTitle">
中国服务机器人市场已占全球市场超1/4
</nut-collapse-item>
<nut-collapse-item :title="title3" :name="3">
QuestMobile:90后互联网用户规模超越80后达3.62亿
</nut-collapse-item>
</nut-collapse>
```
``` javascript
export default {
data() {
return {
activeName: 1,
subTitle: '副标题'
};
}
};
```
### 图标展示
通过`expandIconPosition`可以设置图标的位置,icon设置自定义图标,rotate设置图标旋转的角度
```html
<nut-collapse v-model="activeName" :accordion="true" :expand-icon-position="expandIconPosition" :icon="icon" :rotate="rotate" :icon-width="iconWidth"
:icon-height="iconHeight">
<nut-collapse-item :title="title1" :name="1">
京东数科IPO将引入“绿鞋机制”
</nut-collapse-item>
<nut-collapse-item :title="title2" :name="2">
世界制造业大会开幕,阿里巴巴与安徽合作再升级
</nut-collapse-item>
</nut-collapse>
```
``` javascript
export default {
data() {
return {
activeName: 1,
expandIconPosition: 'left',
icon: 'https://img11.360buyimg.com/imagetools/jfs/t1/132849/8/9709/550/5f5f0d8aE802abee7/68bd02b3a52c3988.png'
rotate: 180,
iconWidth: '20px',
iconHeight: '20px',
};
}
};
```
## Collapse Prop
| 字段 | 说明 | 类型 | 默认值
|----- | ----- | ----- | -----
| v-model | 当前展开面板的 name | 手风琴模式:string \| number<br>非手风琴模式:(string \| number)[] | - |
| accordion | 是否开启手风琴模式 | boolean | false |
| border | 是否显示外边框 | boolean | true |
### Events
| 事件名 | 说明 | 回调参数 |
|------|------|------|
| change | 切换面板时触发 | 类型与 v-model 绑定的值一致 |
### CollapseItem Props
| 参数 | 说明 | 类型 | 默认值 |
|------|------|------|------|
| title | 标题栏左侧内容 | string | - |
| name | 唯一标识符,必填 | string \ number | -1 |
| expand-icon-position | 标题图标的位置 | string | right |
| sub-title | 标题栏副标题 | string | - |
| icon | 标题栏自定义图标链接 | string | - |
| icon-width | 标题栏自定义图标宽度 | string | 24px |
| icon-height | 标题栏自定义图标高度 | string | 12px |
| rotate | 点击折叠和展开的旋转角度,在自定义图标模式下生效 | string \ number | 180 |
\ No newline at end of file
<template>
<view @changeEvt="changeEvt">
<slot></slot>
</view>
</template>
<script lang="ts">
import { toRefs } from 'vue';
import { createComponent } from '@/utils/create';
import { useChildren } from '@/utils/useRelation/useChildren';
export const COLLAPSE_KEY = 'nutCollapse';
const { create } = createComponent('collapse');
export default create({
props: {
active: {
type: [String, Number, Array]
},
accordion: {
type: Boolean
},
expandIconPosition: {
type: String,
default: 'right'
},
icon: {
type: String,
default: ''
},
iconWidth: {
type: String,
default: ''
},
iconHeight: {
type: String,
default: ''
},
rotate: {
type: [String, Number],
default: 180
}
},
setup(props, { emit }) {
const { active } = toRefs(props);
// 多个 item 展开
const changeValAry = (name: any) => {
const activeItem: any = active?.value instanceof Object ? Object.values(active.value) : active?.value;
let index = -1;
activeItem.forEach((item: string | number, idx: number) => {
if (String(item) == String(name)) {
index = idx;
}
});
const v = JSON.parse(JSON.stringify(activeItem));
index > -1 ? v.splice(index, 1) : v.push(name);
emit('update:active', v);
};
// 更新v-modal的值
const changeVal = (val: string | number | Array<string | number>, expanded: boolean) => {
emit('update:active', val);
};
const isExpanded = (name: string | number | Array<string | number>) => {
const { accordion, active } = props;
if (accordion) {
if (typeof active == 'number' || typeof active == 'string') {
return active == name;
} else {
return false;
}
}
};
const { linkChildren } = useChildren(COLLAPSE_KEY);
linkChildren({
value: props.active,
accordion: props.accordion,
expandIconPosition: props.expandIconPosition,
icon: props.icon,
rotate: props.rotate,
changeValAry,
changeVal,
isExpanded
});
}
});
</script>
<style lang="scss">
@import 'index.scss';
</style>
<template>
<div class="demo-list"> </div>
</template>
<script lang="ts"></script>
# CollapseItem 折叠面板
.nut-collapse-item {
position: relative;
.collapse-item {
&::after {
position: absolute;
box-sizing: border-box;
content: ' ';
pointer-events: none;
right: 16px;
bottom: 0;
left: 16px;
border-bottom: 1px solid #ebedf0;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
}
}
.collapse-item {
position: relative;
display: block;
width: 100%;
overflow: hidden;
padding: 13px 26px;
color: #666666;
font-size: 14px;
line-height: 24px;
background-color: #fff;
box-sizing: border-box;
.collapse-icon {
display: block;
position: absolute;
top: 50%;
margin-top: -6px;
right: 16px;
width: 24px;
height: 12px;
line-height: 24px;
background-image: url(https://img13.360buyimg.com/imagetools/jfs/t1/153504/23/6125/464/5fb240abE562ed1c5/382909c698fa3c1f.png);
background-repeat: no-repeat;
background-size: 100% 100%;
transition: transform 0.3s;
}
.col-expanded {
transform: rotate(-180deg);
}
.subTitle {
position: absolute;
top: 50%;
right: 60px;
margin-top: -12px;
color: #969799;
}
}
.collapse-wrapper {
display: block;
position: relative;
height: 0;
overflow: hidden;
transition: height 0.3s ease-in-out;
.collapse-content {
display: block;
padding: 12px 16px;
color: #969799;
font-size: 14px;
line-height: 1.5;
background-color: #fff;
}
}
.nut-collapse-item-disabled {
color: #c8c9cc;
cursor: not-allowed;
pointer-events: none;
.collapse-icon-disabled {
background-image: url(https://img12.360buyimg.com/imagetools/jfs/t1/150103/9/14710/474/5fb2419aE63e79ef3/8db2699c11139027.png);
background-repeat: no-repeat;
background-size: 100% 100%;
}
}
.nut-collapse-item-left {
.collapse-item {
padding: 10px 16px 10px 50px;
.collapse-icon {
left: 20px;
}
.subTitle {
right: 16px;
}
}
}
// .nut-collapse-item.nut-collapse-item-icon {
// .collapse-icon {
// transform: rotate(0deg);
// }
// }
}
<template>
<view :class="['nut-collapse-item', { 'nut-collapse-item-left': classDirection == 'left' }, { 'nut-collapse-item-icon': icon }]">
<view :class="['collapse-item', { 'item-expanded': openExpanded }, { 'nut-collapse-item-disabled': disabled }]" @click="toggleOpen">
<view class="collapse-title">
<view v-html="title"></view>
</view>
<view v-if="subTitle" v-html="subTitle" class="subTitle"></view>
<i v-if="icon" :class="['collapse-icon', { 'col-expanded': openExpanded }, { 'collapse-icon-disabled': disabled }]" :style="iconStyle"></i>
<i v-else :class="['collapse-icon', { 'col-expanded': openExpanded }, { 'collapse-icon-disabled': disabled }]"></i>
</view>
<view class="collapse-wrapper" ref="wrapperRef">
<view class="collapse-content" ref="contentRef">
<slot></slot>
</view>
</view>
</view>
</template>
<script lang="ts">
import { reactive, toRefs, onMounted, ref, nextTick, computed, watch } from 'vue';
import { createComponent } from '@/utils/create';
import { useParent } from '@/utils/useRelation/useParent';
import { COLLAPSE_KEY } from './../collapse/index.vue';
const { create } = createComponent('collapse-item');
export default create({
props: {
title: {
type: String,
default: ''
},
subTitle: {
type: String,
default: ''
},
disabled: {
type: Boolean,
default: false
},
name: {
type: [Number, String],
default: -1,
required: true
},
collapseRef: {
type: Object
}
},
setup(props) {
const collapse = useParent(COLLAPSE_KEY);
const parent: any = reactive(collapse.parent as any);
const index: any = reactive(collapse.index as any);
const proxyData = reactive({
openExpanded: false,
classDirection: 'right',
iconStyle: {
width: '20px',
height: '20px',
'background-image': 'url(https://img10.360buyimg.com/imagetools/jfs/t1/111306/10/17422/341/5f58aa0eEe9218dd6/28d76a42db334e31.png)',
'background-repeat': 'no-repeat',
'background-size': '100% 100%',
transform: 'rotate(0deg)'
}
});
// 获取 Dom 元素
const wrapperRef: any = ref(null);
const contentRef: any = ref(null);
// 清除 willChange 减少性能浪费
const onTransitionEnd = () => {
const wrapperRefEle: any = document.getElementsByClassName('collapse-wrapper')[0];
wrapperRefEle.style.willChange = 'auto';
};
// 手风琴模式
const animation = () => {
const wrapperRefEle: any = wrapperRef.value;
const contentRefEle: any = contentRef.value;
if (!wrapperRefEle || !contentRefEle) {
return;
}
const offsetHeight = contentRefEle.offsetHeight;
if (offsetHeight) {
const contentHeight = `${offsetHeight}px`;
wrapperRefEle.style.willChange = 'height';
wrapperRefEle.style.height = !proxyData.openExpanded ? 0 : contentHeight;
if (parent.icon && !proxyData.openExpanded) {
proxyData.iconStyle['transform'] = 'rotate(0deg)';
} else {
proxyData.iconStyle['transform'] = 'rotate(' + parent.rotate + 'deg)';
}
}
if (!proxyData.openExpanded) {
onTransitionEnd();
}
};
const open = () => {
proxyData.openExpanded = !proxyData.openExpanded;
animation();
};
const defaultOpen = () => {
open();
if (parent.icon) {
proxyData['iconStyle']['transform'] = 'rotate(' + parent.rotate + 'deg)';
}
};
const currentName = computed(() => props.name ?? index.value);
const toggleOpen = () => {
if (parent.accordion) {
parent.children.forEach((item: any, index: number) => {
if (currentName.value == item.name) {
item.changeOpen(!item.openExpanded);
} else {
item.changeOpen(false);
item.animation();
}
});
nextTick(() => {
parent.changeVal(currentName.value, !proxyData.openExpanded);
animation();
});
} else {
parent.changeValAry(props.name);
open();
}
};
// 更改子组件展示
const changeOpen = (bol: boolean) => {
proxyData.openExpanded = bol;
};
const expanded = computed(() => {
if (parent) {
return parent.isExpanded(props.name);
}
return null;
});
watch(expanded, (value, oldValue) => {
if (value) {
proxyData.openExpanded = true;
}
});
onMounted(() => {
const { name } = props;
const active = parent && parent.value;
if (typeof active == 'number' || typeof active == 'string') {
if (name == active) {
defaultOpen();
}
} else if (Object.values(active) instanceof Array) {
const f = Object.values(active).filter(item => item == name);
if (f.length > 0) {
defaultOpen();
}
}
proxyData.classDirection = parent.expandIconPosition;
if (parent.icon) {
proxyData.iconStyle['background-image'] = 'url(' + parent.icon + ')';
}
if (parent.iconWidth) {
proxyData.iconStyle['width'] = parent.conWidth;
}
if (parent.iconHeght) {
proxyData.iconStyle['height'] = parent.iconHeight;
}
});
return {
...toRefs(proxyData),
...toRefs(parent),
wrapperRef,
contentRef,
open,
toggleOpen,
changeOpen,
animation
};
}
});
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>
import {
VNode,
isVNode,
provide,
reactive,
getCurrentInstance,
VNodeNormalizedChildren,
ComponentPublicInstance,
ComponentInternalInstance
} from 'vue';
export function flattenVNodes(children: VNodeNormalizedChildren) {
const result: VNode[] = [];
const traverse = (children: VNodeNormalizedChildren) => {
if (Array.isArray(children)) {
children.forEach(child => {
if (isVNode(child)) {
result.push(child);
if (child.component?.subTree) {
traverse(child.component.subTree.children);
}
if (child.children) {
traverse(child.children);
}
}
});
}
};
traverse(children);
return result;
}
// sort children instances by vnodes order
export function sortChildren(
parent: ComponentInternalInstance,
publicChildren: ComponentPublicInstance[],
internalChildren: ComponentInternalInstance[]
) {
const vnodes = flattenVNodes(parent.subTree.children);
internalChildren.sort((a, b) => vnodes.indexOf(a.vnode) - vnodes.indexOf(b.vnode));
const orderedPublicChildren = internalChildren.map(item => item.proxy!);
publicChildren.sort((a, b) => {
const indexA = orderedPublicChildren.indexOf(a);
const indexB = orderedPublicChildren.indexOf(b);
return indexA - indexB;
});
}
export function useChildren(key: string | symbol) {
const publicChildren: ComponentPublicInstance[] = reactive([]);
const internalChildren: ComponentInternalInstance[] = reactive([]);
const parent = getCurrentInstance()!;
const linkChildren = (value?: any) => {
const link = (child: ComponentInternalInstance) => {
if (child.proxy) {
internalChildren.push(child);
publicChildren.push(child.proxy);
sortChildren(parent, publicChildren, internalChildren);
}
};
const unlink = (child: ComponentInternalInstance) => {
const index = internalChildren.indexOf(child);
publicChildren.splice(index, 1);
internalChildren.splice(index, 1);
};
provide(key, {
link,
unlink,
children: publicChildren,
internalChildren,
...value
});
};
return {
children: publicChildren,
linkChildren
};
}
import { inject, computed, onUnmounted, getCurrentInstance, ComponentPublicInstance, ComponentInternalInstance } from 'vue';
type ParentProvide<T> = T & {
link(child: ComponentInternalInstance): void;
unlink(child: ComponentInternalInstance): void;
children: ComponentPublicInstance[];
internalChildren: ComponentInternalInstance[];
};
export function useParent<T>(key: string | symbol) {
const parent = inject<ParentProvide<T> | null>(key, null);
if (parent) {
const instance = getCurrentInstance();
if (instance) {
const { link, unlink, internalChildren, ...rest } = parent;
link(instance);
onUnmounted(() => {
unlink(instance);
});
const index = computed(() => internalChildren.indexOf(instance));
return {
parent: rest,
index
};
}
}
return {};
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册