UNPKG

shu-c-view

Version:

rollup 打包vue@2.7组件库框架

485 lines (453 loc) 12.3 kB
/** * @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 };