shu-c-view
Version:
rollup 打包vue组件库框架
347 lines (330 loc) • 9.24 kB
JavaScript
/**
* @desc tree 锚点组件
*/
import _isArray from 'lodash/isArray';
import _isEmpty from 'lodash/isEmpty';
import _cloneDeep from 'lodash/cloneDeep';
import { getTreeMap } from '../helper/tree.js';
import { devConsole } from '../helper/util.js';
const BaseTreeAnchor = {
name: 'BaseTreeAnchor',
props: {
// tree数据
list: {
type: Array,
require: true,
default: () => []
},
// 自定义的样式
ctCls: {
type: String
},
// 锚点的标题
title: {
type: String,
default: ''
},
// 数据的格式
props: {
type: Object,
default() {
return {
id: 'id',
name: 'name',
children: 'children',
label: 'label'
};
}
},
// 容器id(必须id)
containerId: {
type: String,
default: ''
},
// 每个子项的class名称
treeItemClass: {
type: String,
default: ''
}
},
watch: {
list: {
handler(newVal, oldVal) {
if (newVal) {
// eslint-disable-next-line no-undef
this.treeData = _cloneDeep(this.list);
}
},
deep: true,
immediate: true
},
activeAnchor: {
handler(newVal, oldVal) {
if (newVal !== oldVal && newVal !== '') {
this.$emit('activeAnchor', newVal);
}
},
immediate: true
}
},
computed: {
planHeight: function() {
const height = this.treeMap.length * 30;
return `height:${height}px`;
},
activeTop: function() {
const height = this.activeIndex * 30 + 4;
return `${height}px`;
},
treeMap: function() {
return getTreeMap(this.list, this.props.children);
}
},
data() {
return {
treeData: [],
activeIndex: 0, // 当前可视区域的下标
activeAnchor: '' // 获取当前的锚点
};
},
created() {
if (this.treeData[0]) {
this.treeData[0].active = true;
this.activeAnchor = this.treeData[0][this.props.id];
}
window.addEventListener('scroll', this.onScroll, true);
},
destroyed() {
window.removeEventListener('scroll', this.onScroll, true);
},
methods: {
/**
* @description 监听页面滚动
*/
onScroll(e) {
const scrollItems = document.querySelectorAll(`.${this.treeItemClass}`);
for (let i = scrollItems.length - 1; i >= 0; i--) {
// 判断滚动条滚动距离是否大于当前滚动项可滚动距离
const judge =
e.target.scrollTop >=
scrollItems[i].offsetTop - scrollItems[0].offsetTop;
if (judge || judge === 0) {
this.activeAnchor = this.judgeScrollBottom()
? this.treeMap[this.treeMap.length - 1][this.props.id]
: this.treeMap[i][this.props.id];
this.getCurrentIndex(this.activeAnchor);
this.setCurrentNode(this.activeAnchor, this.treeData);
break;
}
}
},
/**
* @description 判断是否滚动到底部
*/
judgeScrollBottom() {
// 这里有个坑、想要判断是否到底部 需要给容器定一个高度然后再去判断 不然无法监听
const container = document.querySelector('#' + this.containerId);
if (container) {
const scrollTop = container.scrollTop;
const clientHeight = container.clientHeight;
const scrollHeight = container.scrollHeight;
return Math.ceil(scrollTop) + clientHeight >= scrollHeight;
}
},
/**
* @description 滚动到相对应的区域
*/
goAnchor(id, anchor, item) {
this.getCurrentIndex(id);
this.$nextTick(() => {
document.querySelector(`#${anchor}`).scrollIntoView({
behavior: 'smooth', // 平滑过渡
block: 'start' // 上边框与视窗顶部平齐。默认值
});
});
this.setCurrentNode(id, this.treeData);
this.activeAnchor = id;
},
/**
* @description 修改相对用可是节点的状态
* @param { number } id 节点id
* @param { Array } data 树节点列表
*/
setCurrentNode(id, data) {
for (const item of data) {
item.active = false;
if (id === item.id) {
item.active = true;
}
if (!item.children || !item.children.length) continue;
this.setCurrentNode(id, item.children);
}
},
/**
* @description 获取当前所在的区域
* @param { number } id 节点id
*/
getCurrentIndex(id) {
this.treeMap.map((item, index) => {
if (item.id === id) {
this.activeIndex = index;
}
});
},
/**
* @description 渲染树的子节点
* @param { Array} data 节点数据
* @returns dom
*/
renderChildrenItem(data) {
if (!data || !data.length) return false;
const h = this.$createElement;
return h(
'ul',
{
class: {
'tree-List-sub': true
}
},
[
data.map(item => {
return h('li', {}, [
h(
'p',
{
class: {
'section-link': true,
'section-link-active': item.active
},
on: {
click: event => {
this.goAnchor(
item[this.props.id],
item[this.props.name],
item
);
event.stopPropagation();
}
}
},
[
item[this.props.label],
item[this.props.children]
? this.renderChildrenItem(item[this.props.children])
: ''
]
)
]);
})
]
);
}
},
render(h) {
return h(
'div',
{
class: {
[this.ctCls]: this.ctCls,
'tree-anchor': true
}
},
[
this.title.length > 0
? h(
'div',
{
class: {
'tree-anchor-title': true
}
},
[this.title]
)
: null,
h(
'div',
{
class: {
'tree-anchor-list': true
}
},
[
h(
'div',
{
class: {
'tree-anchor-list-plan': true
},
style: { top: this.planHeight }
},
[
h(
'div',
{
class: { 'active-content': true },
style: { top: this.activeTop }
},
[]
)
]
),
h(
'ul',
{
class: {
'tree-anchor-list-root': true
}
},
[
this.treeData.map(item => {
return h('li', {}, [
h(
'p',
{
class: {
'section-link': true,
'first-selection-link': true,
'section-link-active': item.active
},
attrs: {
href: '#' + item[this.props.name]
},
on: {
click: event => {
this.goAnchor(
item[this.props.id],
item[this.props.name],
item
);
event.stopPropagation();
}
}
},
[item[this.props.label]]
),
item[this.props.children]
? this.renderChildrenItem(item[this.props.children])
: ''
]);
})
]
)
]
)
]
);
}
};
BaseTreeAnchor.install = function(Vue, ElComponents) {
// 用于按需加载的时候独立使用
devConsole('按需加载独立组件:' + BaseTreeAnchor.name);
if (_isArray(ElComponents) && !_isEmpty(ElComponents)) {
for (let i = 0; i < ElComponents.length; i++) {
if (ElComponents[i].name !== BaseTreeAnchor.name) {
Vue.use(ElComponents[i]);
}
}
}
Vue.component(BaseTreeAnchor.name, BaseTreeAnchor);
};
export { BaseTreeAnchor };