shu-c-view
Version:
rollup 打包vue@2.7组件库框架
485 lines (453 loc) • 12.3 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: ''
},
/**
* @description 数据的格式
*/
props: {
type: Object,
default() {
return {
id: 'id',
name: 'name',
children: 'children',
label: 'label'
};
}
},
/**
* @description 展示侧边列表
*/
showList: {
type: Boolean,
default: true
},
/**
* @description 容器id(必须id)
*/
containerId: {
type: String,
default: ''
},
// 滚动区域是否为container
isContainer: {
type: Boolean,
default: true
},
// 每个子项的class名称
treeItemClass: {
type: String,
default: ''
},
// 锚点触发位置
anchorPostion: {
type: String,
default: 'start'
},
// 锚点跳转行为
anchorBehavior: {
type: String,
default: 'smooth'
}
},
watch: {
list: {
handler(newVal) {
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: {
activeTop() {
const height = this.activeIndex * 30 + 4;
return `${height}px`;
},
treeMap() {
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 侧边轴的高度
*/
async planHeight() {
let height = 0;
await this.$nextTick(() => {
height = document.getElementById('tree-anchor-ul').offsetHeight;
});
return `${height}px`;
},
/**
* @description 监听页面滚动
*/
onScroll(e) {
// 当前所有监听的滚动列表
const scrollItems = document.querySelectorAll(`.${this.treeItemClass}`);
let actualHeight = 0;
if (this.isContainer) {
actualHeight = e.target.scrollTop;
} else {
// 当前监听区域的距离顶部的高度
const containerHeight = document.querySelector(`#${this.containerId}`)
.offsetTop;
// 当前容器滚动到顶部的实际距离
actualHeight = e.target.scrollTop - containerHeight;
}
for (let i = scrollItems.length - 1; i >= 0; i--) {
// 判断滚动条滚动距离是否大于当前滚动项可滚动距离
const judge =
actualHeight >= 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 && this.isContainer) {
const scrollTop = container.scrollTop;
const clientHeight = container.clientHeight;
const scrollHeight = container.scrollHeight;
return Math.ceil(scrollTop) + clientHeight >= scrollHeight;
}
const scrollTop = this.getScrollTop();
const clientHeight = this.getWindowHeight();
const scrollHeight = this.getScrollHeight();
return Math.ceil(scrollTop) + clientHeight >= scrollHeight;
},
/**
* @description 获取当前的滚动条在y轴上的高度
* @returns
*/
getScrollTop() {
let scrollTop = 0;
let bodyScrollTop = 0;
let documentScrollTop = 0;
if (document.body) {
bodyScrollTop = document.body.scrollTop;
}
if (document.documentElement) {
documentScrollTop = document.documentElement.scrollTop;
}
scrollTop =
bodyScrollTop - documentScrollTop > 0
? bodyScrollTop
: documentScrollTop;
return scrollTop;
},
/**
* @description 获取当前文档的总高度
* @returns
*/
getScrollHeight() {
let scrollHeight = 0;
let bodyScrollHeight = 0;
let documentScrollHeight = 0;
if (document.body) {
bodyScrollHeight = document.body.scrollHeight;
}
if (document.documentElement) {
documentScrollHeight = document.documentElement.scrollHeight;
}
scrollHeight =
bodyScrollHeight - documentScrollHeight > 0
? bodyScrollHeight
: documentScrollHeight;
return scrollHeight;
},
/**
* @description 获取浏览器视口的高度
* @returns
*/
getWindowHeight() {
let windowHeight = 0;
if (document.compatMode === 'CSS1Compat') {
windowHeight = document.documentElement.clientHeight;
} else {
windowHeight = document.body.clientHeight;
}
return windowHeight;
},
/**
* @description 滚动到相对应的区域
*/
goAnchor(id, anchor) {
this.getCurrentIndex(anchor);
window.removeEventListener('scroll', this.onScroll, true);
this.$nextTick(() => {
document.querySelector(`#${anchor}`).scrollIntoView({
behavior: this.anchorBehavior, // 平滑过渡
block: this.anchorPostion
});
setTimeout(() => {
window.addEventListener('scroll', this.onScroll, true);
}, 1000);
});
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(name) {
// eslint-disable-next-line array-callback-return
this.treeMap.map((item, index) => {
if (item.name === name) {
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
},
style: {
display: this.showList ? 'block' : 'none'
}
},
[
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])
: ''
]
)
]);
})
]
);
},
/**
* @description 渲染列表页
* @returns
*/
renderList() {
const h = this.$createElement;
return [
h(
'div',
{
class: {
'tree-anchor-title': true
},
style: {
display: this.title.length > 0 ? 'block' : 'none'
}
},
[this.title]
),
h(
'div',
{
class: {
'tree-anchor-list': true
}
},
[
h(
'div',
{
class: {
'tree-anchor-list-plan': true
},
style: { height: this.planHeight() }
},
[
h(
'div',
{
class: { 'active-content': true },
style: { top: this.activeTop }
},
[]
)
]
),
h(
'ul',
{
class: {
'tree-anchor-list-root': true
},
attrs: {
id: 'tree-anchor-ul'
}
},
[
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])
: ''
]);
})
]
)
]
)
];
}
},
render(h) {
return h(
'div',
{
class: {
[this.ctCls]: this.ctCls,
'tree-anchor': true
}
},
[this.$slots.default ?? this.renderList()]
);
}
};
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 };