baby 朋友们 又是新的一天 今天开始继续分析起来 今天分析的 collapse 折叠面板 基本的示例
<template>
<div>
<!-- 我们可以看到 这个是由一个父级 多个子选项组成 value来控制当前展开的是哪一些面板 -->
<!-- 子选项呢 用一个name 来标识自己 -->
<!-- ok 接下来来分析实现 -->
<el-collapse v-model="activeNames" @change="handleChange">
<el-collapse-item title="一致性 Consistency" name="1">
<div class="mytest">
<div>与现实生活一致:与现实生活的流程、逻辑保持一致,遵循用户习惯的语言和概念;</div>
</div>
</el-collapse-item>
<el-collapse-item title="反馈 Feedback" name="2">
<div>控制反馈:通过界面样式和交互动效让用户可以清晰的感知自己的操作;</div>
<div>页面反馈:操作后,通过页面元素的变化清晰地展现当前状态。</div>
</el-collapse-item>
<el-collapse-item title="效率 Efficiency" name="3">
<div>简化流程:设计简洁直观的操作流程;</div>
<div>清晰明确:语言表达清晰且表意明确,让用户快速理解进而作出决策;</div>
<div>帮助用户识别:界面简单直白,让用户快速识别而非回忆,减少用户记忆负担。</div>
</el-collapse-item>
<el-collapse-item title="可控 Controllability" name="4">
<div>用户决策:根据场景可给予用户操作建议或安全提示,但不能代替用户进行决策;</div>
<div>结果可控:用户可以自由的进行操作,包括撤销、回退和终止当前操作等。</div>
</el-collapse-item>
</el-collapse>
</div>
</template>
collapse
<template>
<!-- 看这个机构很简单 父级的 -->
<div class="el-collapse" role="tablist" aria-multiselectable="true">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'ElCollapse',
componentName: 'ElCollapse',
props: {
// 是否手风琴模式
accordion: Boolean,
// 当前展开的值
value: {
type: [Array, String, Number],
default() {
return [];
},
},
},
data() {
return {
// 这边内部使用activeNames这个值 对传进来的值做个包装
activeNames: [].concat(this.value),
};
},
provide() {
return {
// 把自己暴露出去 方便子组件更自己 做交互
collapse: this,
};
},
watch: {
value(value) {
// 当值变化了
this.activeNames = [].concat(value);
},
},
methods: {
setActiveNames(activeNames) {
// 设置下这个值然后把这个发送出去
activeNames = [].concat(activeNames);
const value = this.accordion ? activeNames[0] : activeNames;
// 感觉这边没有必要赋值 因为上面不是有 watch value了 value改变 自然this.activeNames 会变化
// 都无所谓吧
this.activeNames = activeNames;
this.$emit('input', value);
this.$emit('change', value);
},
// item参数 是子组件本身
handleItemClick(item) {
// 如果是手风琴模式的话 也就是说当前点击的这个是 开启的状态 其他的都关闭了
if (this.accordion) {
// 等下再来看下
this.setActiveNames(
(this.activeNames[0] || this.activeNames[0] === 0)
&& this.activeNames[0] === item.name
? '' : item.name,
);
} else {
const activeNames = this.activeNames.slice(0);
const index = activeNames.indexOf(item.name);
// 找的到说明是有了 就关闭
if (index > -1) {
activeNames.splice(index, 1);
} else {
activeNames.push(item.name);
}
this.setActiveNames(activeNames);
}
},
},
created() {
// 这边监听这个事件 因为子组件会发射这个事件上来 代表点击了 这个item
this.$on('item-click', this.handleItemClick);
},
};
</script>
collapse-item
<template>
<div class="el-collapse-item"
:class="{'is-active': isActive, 'is-disabled': disabled }">
<div
role="tab"
:aria-expanded="isActive"
:aria-controls="`el-collapse-content-${id}`"
:aria-describedby ="`el-collapse-content-${id}`"
>
<!-- 这边是头部 标题部分 -->
<!-- 一般情况下,onblur事件只在input等元素中才有,而div却没有,因为div没有tabindex属性,所以要给div加上此属性。 -->
<!-- 设置这个之后 tab操作有效果 focusing控制聚焦的时候可以有高亮显示 也可以让这边响应 空格 enter 按键 -->
<div
class="el-collapse-item__header"
@click="handleHeaderClick"
role="button"
:id="`el-collapse-head-${id}`"
:tabindex="disabled ? undefined : 0"
@keyup.space.enter.stop="handleEnterClick"
:class="{
'focusing': focusing,
'is-active': isActive
}"
@focus="handleFocus"
@blur="focusing = false"
>
<!-- 标题插槽 -->
<slot name="title">{{title}}</slot>
<i
class="el-collapse-item__arrow el-icon-arrow-right"
:class="{'is-active': isActive}">
</i>
</div>
</div>
<!-- 这边打开关闭加个动画 等下分析下这个动画 组件 -->
<el-collapse-transition>
<div
class="el-collapse-item__wrap"
style="padding-top: 30px;"
v-show="isActive"
role="tabpanel"
:aria-hidden="!isActive"
:aria-labelledby="`el-collapse-head-${id}`"
:id="`el-collapse-content-${id}`"
>
<div class="el-collapse-item__content">
<slot></slot>
</div>
</div>
</el-collapse-transition>
</div>
</template>
<script>
// 这个动画组件 collapse 展开折叠 的一个动画组件 砸门来分析下 其他东西也没什么了
import ElCollapseTransition from '@/transitions/collapse-transition';
// 这个之前有分析过了
import Emitter from '@/mixins/emitter';
import { generateId } from '@/utils/util';
export default {
name: 'ElCollapseItem',
componentName: 'ElCollapseItem',
mixins: [Emitter],
components: { ElCollapseTransition },
data() {
return {
contentWrapStyle: {
height: 'auto',
display: 'block',
},
contentHeight: 0,
focusing: false,
isClick: false,
id: generateId(), // 随机数取前五位 这感觉怕是不稳啊
// export const generateId = function () {
// return Math.floor(Math.random() * 10000);
// };
};
},
inject: ['collapse'],
props: {
title: String,
name: {
type: [String, Number],
default() {
return this._uid;
},
},
disabled: Boolean,
},
computed: {
isActive() {
return this.collapse.activeNames.indexOf(this.name) > -1;
},
},
methods: {
handleFocus() {
setTimeout(() => {
if (!this.isClick) {
this.focusing = true;
} else {
this.isClick = false;
}
}, 50);
},
// 点击事件
handleHeaderClick() {
if (this.disabled) return;
this.dispatch('ElCollapse', 'item-click', this);
this.focusing = false;
this.isClick = true;
},
// 相当于点击了这个
handleEnterClick() {
// 让ElCollapse 触发 item-click事件
this.dispatch('ElCollapse', 'item-click', this);
},
},
};
</script>
ElCollapseTransition
collapse 展开折叠 的一个动画组件
/* eslint-disable class-methods-use-this */
import { addClass, removeClass } from '@/utils/dom';
const Transition = {
// 进入之前
beforeEnter(el) {
// 加上class
addClass(el, 'collapse-transition');
// .collapse-transition {
// transition: 0.3s height ease-in-out, 0.3s padding-top ease-in-out, 0.3s padding-bottom ease-in-out;
// }
if (!el.dataset) el.dataset = {};
// 保存之前的 padding-top padding-bottom
el.dataset.oldPaddingTop = el.style.paddingTop;
el.dataset.oldPaddingBottom = el.style.paddingBottom;
// 设置
el.style.height = '0';
el.style.paddingTop = 0;
el.style.paddingBottom = 0;
},
enter(el) {
// 保存overflow 属性
el.dataset.oldOverflow = el.style.overflow;
// scrollHeight 这个只读属性是一个元素内容高度的度量,包括由于溢出导致的视图中不可见内容
// 设置高度 然后自然就会有动画了 因为设置了class
if (el.scrollHeight !== 0) {
// console.log(el.scrollHeight, parseInt(el.dataset.oldPaddingTop), Number(el.dataset.oldPaddingBottom));
// 这边是有bug的 可以提个pr了
// 高度计算的有问题因为在之前 el.style.paddingTop以及Bottom 都设置为了0 那么这边获取的话就缺少了这块高度
el.style.height = `${el.scrollHeight}px`;
el.style.paddingTop = el.dataset.oldPaddingTop;
el.style.paddingBottom = el.dataset.oldPaddingBottom;
} else {
el.style.height = '';
el.style.paddingTop = el.dataset.oldPaddingTop;
el.style.paddingBottom = el.dataset.oldPaddingBottom;
}
el.style.overflow = 'hidden';
console.log(el.style.height, 'before');
},
afterEnter(el) {
// for safari: remove class then reset height is necessary
// 进入之后把这些还原
removeClass(el, 'collapse-transition');
el.style.height = '';
// console.log(el.style.height, 'af');
el.style.overflow = el.dataset.oldOverflow;
},
// 要隐藏之前
// 也都是差不多了 离开之前先记住原来的 离开时加上动画的class 离开完之后还原一些属性
beforeLeave(el) {
if (!el.dataset) el.dataset = {};
el.dataset.oldPaddingTop = el.style.paddingTop;
el.dataset.oldPaddingBottom = el.style.paddingBottom;
el.dataset.oldOverflow = el.style.overflow;
el.style.height = `${el.scrollHeight}px`;
el.style.overflow = 'hidden';
},
leave(el) {
if (el.scrollHeight !== 0) {
// for safari: add class after set height, or it will jump to zero height suddenly, weired
addClass(el, 'collapse-transition');
el.style.height = 0;
el.style.paddingTop = 0;
el.style.paddingBottom = 0;
}
},
afterLeave(el) {
removeClass(el, 'collapse-transition');
el.style.height = '';
el.style.overflow = el.dataset.oldOverflow;
el.style.paddingTop = el.dataset.oldPaddingTop;
el.style.paddingBottom = el.dataset.oldPaddingBottom;
},
};
export default {
name: 'ElCollapseTransition',
// 使组件无状态 (没有 data) 和无实例 (没有 this 上下文)。他们用一个简单的 render 函数返回虚拟节点使它们渲染的代价更小。
functional: true,
render(h, { children }) {
const data = {
on: { ...Transition },
};
return h('transition', data, children);
},
};