refactor(scrollbar): 使用ts重构scrollbar组件
This commit is contained in:
parent
1ff8693ced
commit
d1ff6727a1
|
|
@ -275,7 +275,7 @@ yarn log # 生成CHANGELOG
|
|||
- [x] 数据导入导出
|
||||
- [x] 树组件
|
||||
- [x] 可编辑表格
|
||||
- [ ] 系统性能优化
|
||||
- [x] 系统性能优化
|
||||
- [ ] 黑暗主题
|
||||
- [ ] 界面 ui 升级优化
|
||||
- [ ] 兼容最新`vuex`,`vue-router`,
|
||||
|
|
|
|||
|
|
@ -16,112 +16,114 @@ const resolve = require('../../build/getCwdPath');
|
|||
|
||||
function configOptimization(config) {
|
||||
// 只为方便注释 true|| 这样写没意义
|
||||
// config.when(true || isProductionFn(), (config) => {
|
||||
// createScriptExtPlugin(config);
|
||||
config.when(isProductionFn(), (config) => {
|
||||
// createScriptExtPlugin(config);
|
||||
|
||||
config.optimization.runtimeChunk({
|
||||
name: (entry) => `_r-${entry.name}`,
|
||||
});
|
||||
// config.optimization.runtimeChunk('single');
|
||||
config.optimization.splitChunks({
|
||||
chunks: 'all',
|
||||
maxAsyncRequests: 6, //分割后,按需加载的代码块最多允许的并行请求数,在webpack5里默认值变为6
|
||||
maxInitialRequests: 5, //分割后,入口代码块最多允许的并行请求数,在webpack5里默认值变为4
|
||||
// maxInitialRequests: Infinity,
|
||||
automaticNameMaxLength: 15, // 分割chunk自动命名最大长度
|
||||
automaticNameDelimiter: '.', // 分割chunk自动命名分隔符
|
||||
minSize: 30000, // 大小超过30kb的模块才会被提取
|
||||
maxSize: 0, // 只是提示,可以被违反,会尽量将chunk分的比maxSize小,当设为0代表能分则分,分不了不会强制
|
||||
minChunks: 1, //某个模块至少被多少代码块引用,才会被提取成新的chunk
|
||||
name: true, //每个缓存组打包得到的代码块的名称
|
||||
cacheGroups: {
|
||||
default: false,
|
||||
// light: {
|
||||
// name: 'theme-light',
|
||||
// test: (m, c) => {
|
||||
// return (
|
||||
// m.constructor.name === 'CssModule' &&
|
||||
// new RegExp('-light.less|theme=light').test(m._identifier)
|
||||
// );
|
||||
// },
|
||||
// chunks: 'all',
|
||||
// enforce: true,
|
||||
// priority: 40,
|
||||
// },
|
||||
// dark: {
|
||||
// name: 'theme-dark',
|
||||
// test: (m, c) => {
|
||||
// return (
|
||||
// m.constructor.name === 'CssModule' &&
|
||||
// new RegExp('-dark.less|theme=dark').test(m._identifier)
|
||||
// );
|
||||
// },
|
||||
// chunks: 'all',
|
||||
// enforce: true,
|
||||
// priority: 40,
|
||||
// },
|
||||
// styles: {
|
||||
// name: 'styles',
|
||||
// test: /\.(css|scss|sass|less|styl)$/,
|
||||
// chunks: 'async',
|
||||
// enforce: true,
|
||||
// priority: 30,
|
||||
// },
|
||||
commons: {
|
||||
name: 'commons.chunk',
|
||||
test: resolve('src/components'),
|
||||
minChunks: 2,
|
||||
priority: 20,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
vendor: {
|
||||
priority: 10,
|
||||
reuseExistingChunk: true,
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
name(module) {
|
||||
const match = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)(.*?)([\\/]|$)/);
|
||||
let packageName = match[1];
|
||||
// if (match[1] === '@xxx') {
|
||||
// packageName += `-${match[3]}`;
|
||||
// }
|
||||
if (
|
||||
packageName.indexOf('axios') !== -1 ||
|
||||
packageName.indexOf('babel') !== -1 ||
|
||||
packageName.indexOf('crypto-js') !== -1 ||
|
||||
packageName.indexOf('moment') !== -1 ||
|
||||
packageName.indexOf('lodash') !== -1 ||
|
||||
packageName.indexOf('mutationobserver') !== -1 ||
|
||||
packageName.indexOf('resize-observer-polyfill') !== -1 ||
|
||||
packageName.indexOf('vue') !== -1 ||
|
||||
packageName.indexOf('dom-align') !== -1 ||
|
||||
packageName.indexOf('async-validator') !== -1 ||
|
||||
packageName.indexOf('readable-stream') !== -1 ||
|
||||
packageName.indexOf('browserify-sign') !== -1 ||
|
||||
packageName.indexOf('regenerator-runtime') !== -1 ||
|
||||
packageName.indexOf('core-js') !== -1
|
||||
) {
|
||||
packageName = 'entry-lib';
|
||||
} else if (packageName.indexOf('ant-design') !== -1) {
|
||||
packageName = 'design';
|
||||
} else if (
|
||||
packageName.indexOf('echarts') !== -1 ||
|
||||
packageName.indexOf('zrender') !== -1 ||
|
||||
packageName.indexOf('vue-baidu-map') !== -1
|
||||
) {
|
||||
packageName = 'chart';
|
||||
} else if (packageName.indexOf('xlsx') !== -1) {
|
||||
packageName = 'xlsx';
|
||||
}
|
||||
// else {
|
||||
// packageName = 'vendor';
|
||||
// }
|
||||
// const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
|
||||
return `${packageName.replace('@', '')}.chunk`;
|
||||
config.optimization.runtimeChunk({
|
||||
name: (entry) => `_r-${entry.name}`,
|
||||
});
|
||||
// config.optimization.runtimeChunk('single');
|
||||
config.optimization.splitChunks({
|
||||
chunks: 'all',
|
||||
maxAsyncRequests: 6, //分割后,按需加载的代码块最多允许的并行请求数,在webpack5里默认值变为6
|
||||
maxInitialRequests: 5, //分割后,入口代码块最多允许的并行请求数,在webpack5里默认值变为4
|
||||
// maxInitialRequests: Infinity,
|
||||
automaticNameMaxLength: 15, // 分割chunk自动命名最大长度
|
||||
automaticNameDelimiter: '.', // 分割chunk自动命名分隔符
|
||||
minSize: 30000, // 大小超过30kb的模块才会被提取
|
||||
maxSize: 0, // 只是提示,可以被违反,会尽量将chunk分的比maxSize小,当设为0代表能分则分,分不了不会强制
|
||||
minChunks: 1, //某个模块至少被多少代码块引用,才会被提取成新的chunk
|
||||
name: true, //每个缓存组打包得到的代码块的名称
|
||||
cacheGroups: {
|
||||
default: false,
|
||||
// light: {
|
||||
// name: 'theme-light',
|
||||
// test: (m, c) => {
|
||||
// return (
|
||||
// m.constructor.name === 'CssModule' &&
|
||||
// new RegExp('-light.less|theme=light').test(m._identifier)
|
||||
// );
|
||||
// },
|
||||
// chunks: 'all',
|
||||
// enforce: true,
|
||||
// priority: 40,
|
||||
// },
|
||||
// dark: {
|
||||
// name: 'theme-dark',
|
||||
// test: (m, c) => {
|
||||
// return (
|
||||
// m.constructor.name === 'CssModule' &&
|
||||
// new RegExp('-dark.less|theme=dark').test(m._identifier)
|
||||
// );
|
||||
// },
|
||||
// chunks: 'all',
|
||||
// enforce: true,
|
||||
// priority: 40,
|
||||
// },
|
||||
// styles: {
|
||||
// name: 'styles',
|
||||
// test: /\.(css|scss|sass|less|styl)$/,
|
||||
// chunks: 'async',
|
||||
// enforce: true,
|
||||
// priority: 30,
|
||||
// },
|
||||
commons: {
|
||||
name: 'commons.chunk',
|
||||
test: resolve('src/components'),
|
||||
minChunks: 2,
|
||||
priority: 20,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
vendor: {
|
||||
priority: 10,
|
||||
reuseExistingChunk: true,
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
name(module) {
|
||||
const match = module.context.match(
|
||||
/[\\/]node_modules[\\/](.*?)([\\/]|$)(.*?)([\\/]|$)/
|
||||
);
|
||||
let packageName = match[1];
|
||||
// if (match[1] === '@xxx') {
|
||||
// packageName += `-${match[3]}`;
|
||||
// }
|
||||
if (
|
||||
packageName.indexOf('axios') !== -1 ||
|
||||
packageName.indexOf('babel') !== -1 ||
|
||||
packageName.indexOf('crypto-js') !== -1 ||
|
||||
packageName.indexOf('moment') !== -1 ||
|
||||
packageName.indexOf('lodash') !== -1 ||
|
||||
packageName.indexOf('mutationobserver') !== -1 ||
|
||||
packageName.indexOf('resize-observer-polyfill') !== -1 ||
|
||||
packageName.indexOf('vue') !== -1 ||
|
||||
packageName.indexOf('dom-align') !== -1 ||
|
||||
packageName.indexOf('async-validator') !== -1 ||
|
||||
packageName.indexOf('readable-stream') !== -1 ||
|
||||
packageName.indexOf('browserify-sign') !== -1 ||
|
||||
packageName.indexOf('regenerator-runtime') !== -1 ||
|
||||
packageName.indexOf('core-js') !== -1
|
||||
) {
|
||||
packageName = 'entry-lib';
|
||||
} else if (packageName.indexOf('ant-design') !== -1) {
|
||||
packageName = 'design';
|
||||
} else if (
|
||||
packageName.indexOf('echarts') !== -1 ||
|
||||
packageName.indexOf('zrender') !== -1 ||
|
||||
packageName.indexOf('vue-baidu-map') !== -1
|
||||
) {
|
||||
packageName = 'chart';
|
||||
} else if (packageName.indexOf('xlsx') !== -1) {
|
||||
packageName = 'xlsx';
|
||||
}
|
||||
// else {
|
||||
// packageName = 'vendor';
|
||||
// }
|
||||
// const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
|
||||
return `${packageName.replace('@', '')}.chunk`;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
// });
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@
|
|||
|
||||
<script lang="tsx">
|
||||
// component
|
||||
import { defineComponent, PropOptions, ref, unref } from 'compatible-vue';
|
||||
import { defineComponent, PropOptions, ref, unref, nextTick } from 'compatible-vue';
|
||||
import { Scrollbar } from '@/components/scrollbar';
|
||||
|
||||
// hook
|
||||
import { useDesign } from '@/hooks/core/useDesign';
|
||||
|
|
@ -20,6 +21,7 @@
|
|||
import { TypeEnum } from './types';
|
||||
export default defineComponent({
|
||||
name: 'ScrollContainer',
|
||||
components: { Scrollbar },
|
||||
props: {
|
||||
// 滚动类型
|
||||
type: {
|
||||
|
|
@ -33,12 +35,14 @@
|
|||
const scrollbarRef = ref<any>(null);
|
||||
|
||||
function scrollTo(to: number, duration = 500) {
|
||||
const { start } = useScrollTo({
|
||||
el: unref(unref(scrollbarRef).wrap),
|
||||
to,
|
||||
duration,
|
||||
nextTick(() => {
|
||||
const { start } = useScrollTo({
|
||||
el: unref(unref(scrollbarRef).wrap),
|
||||
to,
|
||||
duration,
|
||||
});
|
||||
start();
|
||||
});
|
||||
start();
|
||||
}
|
||||
|
||||
function getScrollWrap() {
|
||||
|
|
@ -46,12 +50,14 @@
|
|||
}
|
||||
|
||||
function scrollBottom() {
|
||||
const scrollHeight = unref(scrollbarRef).wrap.scrollHeight as number;
|
||||
const { start } = useScrollTo({
|
||||
el: unref(unref(scrollbarRef).wrap),
|
||||
to: scrollHeight,
|
||||
nextTick(() => {
|
||||
const scrollHeight = unref(scrollbarRef).wrap.scrollHeight as number;
|
||||
const { start } = useScrollTo({
|
||||
el: unref(unref(scrollbarRef).wrap),
|
||||
to: scrollHeight,
|
||||
});
|
||||
start();
|
||||
});
|
||||
start();
|
||||
}
|
||||
return {
|
||||
prefixCls,
|
||||
|
|
|
|||
|
|
@ -1,71 +0,0 @@
|
|||
.el-scrollbar {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.el-scrollbar:active > .el-scrollbar__bar,
|
||||
.el-scrollbar:focus > .el-scrollbar__bar,
|
||||
.el-scrollbar:hover > .el-scrollbar__bar {
|
||||
opacity: 1;
|
||||
-webkit-transition: opacity 340ms ease-out;
|
||||
transition: opacity 340ms ease-out;
|
||||
}
|
||||
|
||||
.el-scrollbar__wrap {
|
||||
height: 100%;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.el-scrollbar__wrap--hidden-default {
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.el-scrollbar__wrap--hidden-default::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.el-scrollbar__thumb {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
cursor: pointer;
|
||||
background-color: rgba(144, 147, 153, 0.3);
|
||||
border-radius: inherit;
|
||||
-webkit-transition: 0.3s background-color;
|
||||
transition: 0.3s background-color;
|
||||
}
|
||||
|
||||
.el-scrollbar__thumb:hover {
|
||||
background-color: rgba(144, 147, 153, 0.5);
|
||||
}
|
||||
|
||||
.el-scrollbar__bar {
|
||||
position: absolute;
|
||||
right: 2px;
|
||||
bottom: 2px;
|
||||
z-index: 1;
|
||||
border-radius: 4px;
|
||||
opacity: 0;
|
||||
-webkit-transition: opacity 120ms ease-out;
|
||||
transition: opacity 120ms ease-out;
|
||||
}
|
||||
|
||||
.el-scrollbar__bar.is-vertical {
|
||||
top: 2px;
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.el-scrollbar__bar.is-vertical > div {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.el-scrollbar__bar.is-horizontal {
|
||||
left: 2px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
.el-scrollbar__bar.is-horizontal > div {
|
||||
height: 100%;
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
/**
|
||||
* copy from element-ui
|
||||
*/
|
||||
|
||||
import Scrollbar from './src/main';
|
||||
import './index.css';
|
||||
/* istanbul ignore next */
|
||||
Scrollbar.install = function (Vue) {
|
||||
Vue.component('Scrollbar', Scrollbar);
|
||||
};
|
||||
|
||||
export default Scrollbar;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* copy from element-ui
|
||||
*/
|
||||
|
||||
export { default as Scrollbar } from './src/main';
|
||||
|
|
@ -1,100 +0,0 @@
|
|||
import { renderThumbStyle, BAR_MAP, on, off } from './util';
|
||||
import { defineComponent } from 'compatible-vue';
|
||||
/* istanbul ignore next */
|
||||
export default defineComponent({
|
||||
name: 'Bar',
|
||||
|
||||
props: {
|
||||
vertical: Boolean,
|
||||
size: String,
|
||||
move: Number,
|
||||
},
|
||||
watch: {
|
||||
size(size) {
|
||||
// @ts-ignore
|
||||
this.$el.style.display = size && size !== '0' ? '' : 'none';
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
bar() {
|
||||
return BAR_MAP[this.vertical ? 'vertical' : 'horizontal'];
|
||||
},
|
||||
|
||||
wrap() {
|
||||
return this.$parent.wrap;
|
||||
},
|
||||
},
|
||||
|
||||
render(h) {
|
||||
const { size, move, bar } = this;
|
||||
|
||||
return (
|
||||
<div class={['el-scrollbar__bar', 'is-' + bar.key]} onMousedown={this.clickTrackHandler}>
|
||||
<div
|
||||
ref="thumb"
|
||||
class="el-scrollbar__thumb"
|
||||
onMousedown={this.clickThumbHandler}
|
||||
style={renderThumbStyle({ size, move, bar })}
|
||||
></div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
methods: {
|
||||
clickThumbHandler(e) {
|
||||
// prevent click event of right button
|
||||
if (e.ctrlKey || e.button === 2) {
|
||||
return;
|
||||
}
|
||||
this.startDrag(e);
|
||||
this[this.bar.axis] =
|
||||
e.currentTarget[this.bar.offset] -
|
||||
(e[this.bar.client] - e.currentTarget.getBoundingClientRect()[this.bar.direction]);
|
||||
},
|
||||
|
||||
clickTrackHandler(e) {
|
||||
const offset = Math.abs(
|
||||
e.target.getBoundingClientRect()[this.bar.direction] - e[this.bar.client]
|
||||
);
|
||||
const thumbHalf = this.$refs.thumb[this.bar.offset] / 2;
|
||||
const thumbPositionPercentage = ((offset - thumbHalf) * 100) / this.$el[this.bar.offset];
|
||||
|
||||
this.wrap[this.bar.scroll] = (thumbPositionPercentage * this.wrap[this.bar.scrollSize]) / 100;
|
||||
},
|
||||
|
||||
startDrag(e) {
|
||||
e.stopImmediatePropagation();
|
||||
this.cursorDown = true;
|
||||
|
||||
on(document, 'mousemove', this.mouseMoveDocumentHandler);
|
||||
on(document, 'mouseup', this.mouseUpDocumentHandler);
|
||||
document.onselectstart = () => false;
|
||||
},
|
||||
|
||||
mouseMoveDocumentHandler(e) {
|
||||
if (this.cursorDown === false) return;
|
||||
const prevPage = this[this.bar.axis];
|
||||
|
||||
if (!prevPage) return;
|
||||
|
||||
const offset =
|
||||
(this.$el.getBoundingClientRect()[this.bar.direction] - e[this.bar.client]) * -1;
|
||||
const thumbClickPosition = this.$refs.thumb[this.bar.offset] - prevPage;
|
||||
const thumbPositionPercentage =
|
||||
((offset - thumbClickPosition) * 100) / this.$el[this.bar.offset];
|
||||
|
||||
this.wrap[this.bar.scroll] = (thumbPositionPercentage * this.wrap[this.bar.scrollSize]) / 100;
|
||||
},
|
||||
|
||||
mouseUpDocumentHandler() {
|
||||
this.cursorDown = false;
|
||||
this[this.bar.axis] = 0;
|
||||
off(document, 'mousemove', this.mouseMoveDocumentHandler);
|
||||
document.onselectstart = null;
|
||||
},
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
off(document, 'mouseup', this.mouseUpDocumentHandler);
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
import { renderThumbStyle, BAR_MAP } from './util';
|
||||
import {
|
||||
defineComponent,
|
||||
PropOptions,
|
||||
computed,
|
||||
unref,
|
||||
inject,
|
||||
Ref,
|
||||
reactive,
|
||||
ref,
|
||||
onBeforeUnmount,
|
||||
} from 'compatible-vue';
|
||||
import { on, off } from '@/utils/domUtils';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Bar',
|
||||
props: {
|
||||
vertical: Boolean as PropOptions<boolean>,
|
||||
size: String as PropOptions<string>,
|
||||
move: Number as PropOptions<number>,
|
||||
},
|
||||
setup(props) {
|
||||
const thumbRef = ref<Nullable<HTMLDivElement>>(null);
|
||||
const elRef = ref<Nullable<HTMLDivElement>>(null);
|
||||
const commonState = reactive<KeyString>({});
|
||||
const getBarRef = computed(() => {
|
||||
return BAR_MAP[props.vertical ? 'vertical' : 'horizontal'];
|
||||
});
|
||||
const parentElRef = inject('scroll-bar-wrap') as Ref<Nullable<HTMLDivElement>>;
|
||||
|
||||
function clickThumbHandler(e: MouseEvent & ChangeEvent) {
|
||||
const { ctrlKey, button, currentTarget } = e;
|
||||
// prevent click event of right button
|
||||
if (ctrlKey || button === 2 || !currentTarget) {
|
||||
return;
|
||||
}
|
||||
startDrag(e);
|
||||
const bar = unref(getBarRef);
|
||||
commonState[bar.axis] =
|
||||
currentTarget[bar.offset] -
|
||||
(e[bar.client] -
|
||||
((currentTarget as unknown) as Element).getBoundingClientRect()[bar.direction]);
|
||||
}
|
||||
|
||||
function clickTrackHandler(e: ChangeEvent) {
|
||||
const bar = unref(getBarRef);
|
||||
const offset = Math.abs(e.target.getBoundingClientRect()[bar.direction] - e[bar.client]);
|
||||
const thumbEl = unref(thumbRef);
|
||||
const parentEl = unref(parentElRef);
|
||||
const el = unref(elRef);
|
||||
if (!thumbEl || !el || !parentEl) return;
|
||||
const thumbHalf = thumbEl[bar.offset] / 2;
|
||||
const thumbPositionPercentage = ((offset - thumbHalf) * 100) / el[bar.offset];
|
||||
parentEl[bar.scroll] = (thumbPositionPercentage * parentEl[bar.scrollSize]) / 100;
|
||||
}
|
||||
|
||||
function startDrag(e: Event) {
|
||||
e.stopImmediatePropagation();
|
||||
commonState.cursorDown = true;
|
||||
|
||||
on(document, 'mousemove', mouseMoveDocumentHandler);
|
||||
on(document, 'mouseup', mouseUpDocumentHandler);
|
||||
document.onselectstart = () => false;
|
||||
}
|
||||
|
||||
function mouseMoveDocumentHandler(e: ChangeEvent) {
|
||||
if (commonState.cursorDown === false) return;
|
||||
const bar = unref(getBarRef);
|
||||
const prevPage = commonState[bar.axis];
|
||||
const el = unref(elRef);
|
||||
const parentEl = unref(parentElRef);
|
||||
const thumbEl = unref(thumbRef);
|
||||
if (!prevPage || !el || !thumbEl || !parentEl) return;
|
||||
|
||||
const offset = (el.getBoundingClientRect()[bar.direction] - e[bar.client]) * -1;
|
||||
const thumbClickPosition = thumbEl[bar.offset] - prevPage;
|
||||
const thumbPositionPercentage = ((offset - thumbClickPosition) * 100) / el[bar.offset];
|
||||
|
||||
parentEl[bar.scroll] = (thumbPositionPercentage * parentEl[bar.scrollSize]) / 100;
|
||||
}
|
||||
|
||||
function mouseUpDocumentHandler() {
|
||||
const bar = unref(getBarRef);
|
||||
commonState.cursorDown = false;
|
||||
commonState[bar.axis] = 0;
|
||||
off(document, 'mousemove', mouseMoveDocumentHandler);
|
||||
document.onselectstart = null;
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
off(document, 'mouseup', mouseUpDocumentHandler);
|
||||
});
|
||||
return () => {
|
||||
const bar = unref(getBarRef);
|
||||
const { size, move } = props;
|
||||
return (
|
||||
<div
|
||||
class={['scrollbar__bar', 'is-' + bar.key]}
|
||||
onMousedown={clickTrackHandler}
|
||||
ref={elRef}
|
||||
>
|
||||
<div
|
||||
ref={thumbRef}
|
||||
class="scrollbar__thumb"
|
||||
onMousedown={clickThumbHandler}
|
||||
style={renderThumbStyle({ size, move, bar })}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
.scrollbar {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&__wrap {
|
||||
height: 100%;
|
||||
overflow: scroll;
|
||||
|
||||
&--hidden-default {
|
||||
scrollbar-width: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__thumb {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
cursor: pointer;
|
||||
background-color: rgba(144, 147, 153, 0.3);
|
||||
border-radius: inherit;
|
||||
transition: 0.3s background-color;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(144, 147, 153, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
&__bar {
|
||||
position: absolute;
|
||||
right: 2px;
|
||||
bottom: 2px;
|
||||
z-index: 1;
|
||||
border-radius: 4px;
|
||||
opacity: 0;
|
||||
-webkit-transition: opacity 120ms ease-out;
|
||||
transition: opacity 120ms ease-out;
|
||||
|
||||
&.is-vertical {
|
||||
top: 2px;
|
||||
width: 6px;
|
||||
|
||||
& > div {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-horizontal {
|
||||
left: 2px;
|
||||
height: 6px;
|
||||
|
||||
& > div {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.scrollbar:active > .scrollbar__bar,
|
||||
.scrollbar:focus > .scrollbar__bar,
|
||||
.scrollbar:hover > .scrollbar__bar {
|
||||
opacity: 1;
|
||||
transition: opacity 280ms ease-out;
|
||||
}
|
||||
|
|
@ -1,138 +0,0 @@
|
|||
// reference https://github.com/noeldelgado/gemini-scrollbar/blob/master/index.js
|
||||
|
||||
import { addResizeListener, removeResizeListener } from './resize-event';
|
||||
import scrollbarWidth from './scrollbar-width';
|
||||
import { toObject } from './util';
|
||||
import Bar from './bar';
|
||||
|
||||
/* istanbul ignore next */
|
||||
export default {
|
||||
name: 'Scrollbar',
|
||||
|
||||
components: { Bar },
|
||||
|
||||
props: {
|
||||
native: Boolean,
|
||||
wrapStyle: {},
|
||||
wrapClass: {},
|
||||
viewClass: {},
|
||||
viewStyle: {},
|
||||
noresize: Boolean, // 如果 container 尺寸不会发生变化,最好设置它可以优化性能
|
||||
tag: {
|
||||
type: String,
|
||||
default: 'div',
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
sizeWidth: '0',
|
||||
sizeHeight: '0',
|
||||
moveX: 0,
|
||||
moveY: 0,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
wrap() {
|
||||
return this.$refs.wrap;
|
||||
},
|
||||
},
|
||||
|
||||
render(h) {
|
||||
const gutter = scrollbarWidth();
|
||||
let style = this.wrapStyle;
|
||||
|
||||
if (gutter) {
|
||||
const gutterWith = `-${gutter}px`;
|
||||
const gutterStyle = `margin-bottom: ${gutterWith}; margin-right: ${gutterWith};`;
|
||||
|
||||
if (Array.isArray(this.wrapStyle)) {
|
||||
style = toObject(this.wrapStyle);
|
||||
style.marginRight = style.marginBottom = gutterWith;
|
||||
} else if (typeof this.wrapStyle === 'string') {
|
||||
style += gutterStyle;
|
||||
} else {
|
||||
style = gutterStyle;
|
||||
}
|
||||
}
|
||||
const view = h(
|
||||
this.tag,
|
||||
{
|
||||
class: ['el-scrollbar__view', this.viewClass],
|
||||
style: this.viewStyle,
|
||||
ref: 'resize',
|
||||
},
|
||||
this.$slots.default
|
||||
);
|
||||
const wrap = (
|
||||
<div
|
||||
ref="wrap"
|
||||
style={style}
|
||||
onScroll={this.handleScroll}
|
||||
class={[
|
||||
this.wrapClass,
|
||||
'el-scrollbar__wrap',
|
||||
gutter ? '' : 'el-scrollbar__wrap--hidden-default',
|
||||
]}
|
||||
>
|
||||
{[view]}
|
||||
</div>
|
||||
);
|
||||
let nodes;
|
||||
|
||||
if (!this.native) {
|
||||
nodes = [
|
||||
wrap,
|
||||
/* eslint-disable */
|
||||
<Bar move={this.moveX} size={this.sizeWidth}></Bar>,
|
||||
<Bar vertical move={this.moveY} size={this.sizeHeight}></Bar>,
|
||||
];
|
||||
} else {
|
||||
nodes = [
|
||||
<div ref="wrap" class={[this.wrapClass, 'el-scrollbar__wrap']} style={style}>
|
||||
{[view]}
|
||||
</div>,
|
||||
];
|
||||
}
|
||||
return h('div', { class: 'el-scrollbar' }, nodes);
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleScroll() {
|
||||
const wrap = this.wrap;
|
||||
|
||||
this.moveY = (wrap.scrollTop * 100) / wrap.clientHeight;
|
||||
this.moveX = (wrap.scrollLeft * 100) / wrap.clientWidth;
|
||||
},
|
||||
|
||||
update() {
|
||||
let heightPercentage, widthPercentage;
|
||||
const wrap = this.wrap;
|
||||
if (!wrap) return;
|
||||
|
||||
heightPercentage = (wrap.clientHeight * 100) / wrap.scrollHeight;
|
||||
widthPercentage = (wrap.clientWidth * 100) / wrap.scrollWidth;
|
||||
|
||||
this.sizeHeight = heightPercentage < 100 ? heightPercentage + '%' : '';
|
||||
this.sizeWidth = widthPercentage < 100 ? widthPercentage + '%' : '';
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.native) return;
|
||||
this.$nextTick(this.update);
|
||||
if (!this.noresize) {
|
||||
addResizeListener(this.$refs.resize, this.update);
|
||||
addResizeListener(this.$refs.wrap, this.update);
|
||||
}
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
if (this.native) return;
|
||||
if (!this.noresize) {
|
||||
removeResizeListener(this.$refs.resize, this.update);
|
||||
removeResizeListener(this.$refs.wrap, this.update);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
import { addResizeListener, removeResizeListener } from '@/utils/event/resizeEvent';
|
||||
import scrollbarWidth from '@/utils/scrollbarWidth';
|
||||
import { toObject } from './util';
|
||||
import Bar from './bar';
|
||||
import { isString } from '@/utils/is/index';
|
||||
import './index.less';
|
||||
import {
|
||||
defineComponent,
|
||||
PropOptions,
|
||||
unref,
|
||||
reactive,
|
||||
ref,
|
||||
provide,
|
||||
onMounted,
|
||||
nextTick,
|
||||
onBeforeUnmount,
|
||||
getCurrentInstance,
|
||||
} from 'compatible-vue';
|
||||
import { getSlot } from '@/utils/helper/tsxHelper';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Scrollbar',
|
||||
props: {
|
||||
native: Boolean as PropOptions<boolean>,
|
||||
wrapStyle: {} as PropOptions<any>,
|
||||
wrapClass: {} as PropOptions<any>,
|
||||
viewClass: {} as PropOptions<any>,
|
||||
viewStyle: {} as PropOptions<any>,
|
||||
noresize: Boolean as PropOptions<boolean>, // 如果 container 尺寸不会发生变化,最好设置它可以优化性能
|
||||
tag: {
|
||||
type: String,
|
||||
default: 'div',
|
||||
} as PropOptions<string>,
|
||||
},
|
||||
setup(props, { slots }) {
|
||||
const resizeRef = ref<Nullable<HTMLDivElement>>(null);
|
||||
const wrapElRef = ref<Nullable<HTMLDivElement>>(null);
|
||||
provide('scroll-bar-wrap', wrapElRef);
|
||||
const state = reactive({
|
||||
sizeWidth: '0',
|
||||
sizeHeight: '0',
|
||||
moveX: 0,
|
||||
moveY: 0,
|
||||
});
|
||||
|
||||
function handleScroll() {
|
||||
const warpEl = unref(wrapElRef);
|
||||
if (!warpEl) return;
|
||||
const { scrollTop, scrollLeft, clientHeight, clientWidth } = warpEl;
|
||||
|
||||
state.moveY = (scrollTop * 100) / clientHeight;
|
||||
state.moveX = (scrollLeft * 100) / clientWidth;
|
||||
}
|
||||
function update() {
|
||||
const warpEl = unref(wrapElRef);
|
||||
if (!warpEl) return;
|
||||
const { scrollHeight, scrollWidth, clientHeight, clientWidth } = warpEl;
|
||||
const heightPercentage = (clientHeight * 100) / scrollHeight;
|
||||
const widthPercentage = (clientWidth * 100) / scrollWidth;
|
||||
|
||||
state.sizeHeight = heightPercentage < 100 ? heightPercentage + '%' : '';
|
||||
state.sizeWidth = widthPercentage < 100 ? widthPercentage + '%' : '';
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const currentInstance = getCurrentInstance() as any;
|
||||
if (currentInstance) {
|
||||
currentInstance.wrap = unref(wrapElRef);
|
||||
}
|
||||
const { native, noresize } = props;
|
||||
const resizeEl = unref(resizeRef);
|
||||
const warpEl = unref(wrapElRef);
|
||||
if (native || !resizeEl || !warpEl) return;
|
||||
nextTick(update);
|
||||
if (!noresize) {
|
||||
addResizeListener(resizeEl, update);
|
||||
addResizeListener(warpEl, update);
|
||||
}
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
const { native, noresize } = props;
|
||||
const resizeEl = unref(resizeRef);
|
||||
const warpEl = unref(wrapElRef);
|
||||
if (native || !resizeEl || !warpEl) return;
|
||||
if (!noresize) {
|
||||
removeResizeListener(resizeEl, update);
|
||||
removeResizeListener(warpEl, update);
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
const { native, tag, viewClass, viewStyle, wrapClass, wrapStyle } = props;
|
||||
let style = wrapStyle;
|
||||
const gutter = scrollbarWidth();
|
||||
|
||||
if (gutter) {
|
||||
const gutterWith = `-${gutter}px`;
|
||||
const gutterStyle = `margin-bottom: ${gutterWith}; margin-right: ${gutterWith};`;
|
||||
|
||||
if (Array.isArray(wrapStyle)) {
|
||||
style = toObject(wrapStyle);
|
||||
style.marginRight = style.marginBottom = gutterWith;
|
||||
} else if (isString(wrapStyle)) {
|
||||
style += gutterStyle;
|
||||
} else {
|
||||
style = gutterStyle;
|
||||
}
|
||||
}
|
||||
|
||||
const Tag = tag as any;
|
||||
const view = (
|
||||
<Tag class={['scrollbar__view', viewClass]} style={viewStyle} ref={resizeRef}>
|
||||
{getSlot(slots)}
|
||||
</Tag>
|
||||
);
|
||||
const wrap = (
|
||||
<div
|
||||
ref={wrapElRef}
|
||||
style={style}
|
||||
onScroll={handleScroll}
|
||||
class={[wrapClass, 'scrollbar__wrap', gutter ? '' : 'scrollbar__wrap--hidden-default']}
|
||||
>
|
||||
{[view]}
|
||||
</div>
|
||||
);
|
||||
let nodes: any[] = [];
|
||||
const { moveX, sizeWidth, moveY, sizeHeight } = state;
|
||||
if (!native) {
|
||||
nodes = [
|
||||
wrap,
|
||||
/* eslint-disable */
|
||||
<Bar move={moveX} size={sizeWidth}></Bar>,
|
||||
<Bar vertical move={moveY} size={sizeHeight}></Bar>,
|
||||
];
|
||||
} else {
|
||||
nodes = [
|
||||
<div ref="wrap" class={[wrapClass, 'scrollbar__wrap']} style={style}>
|
||||
{[view]}
|
||||
</div>,
|
||||
];
|
||||
}
|
||||
return <div class="scrollbar">{nodes}</div>;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
|
||||
const isServer = typeof window === 'undefined';
|
||||
|
||||
/* istanbul ignore next */
|
||||
const resizeHandler = function (entries) {
|
||||
for (const entry of entries) {
|
||||
const listeners = entry.target.__resizeListeners__ || [];
|
||||
if (listeners.length) {
|
||||
listeners.forEach((fn) => {
|
||||
fn();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* istanbul ignore next */
|
||||
export const addResizeListener = function (element, fn) {
|
||||
if (isServer) return;
|
||||
if (!element.__resizeListeners__) {
|
||||
element.__resizeListeners__ = [];
|
||||
element.__ro__ = new ResizeObserver(resizeHandler);
|
||||
element.__ro__.observe(element);
|
||||
}
|
||||
element.__resizeListeners__.push(fn);
|
||||
};
|
||||
|
||||
/* istanbul ignore next */
|
||||
export const removeResizeListener = function (element, fn) {
|
||||
if (!element || !element.__resizeListeners__) return;
|
||||
element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1);
|
||||
if (!element.__resizeListeners__.length) {
|
||||
element.__ro__.disconnect();
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
export interface BarMapItem {
|
||||
offset: string;
|
||||
scroll: string;
|
||||
scrollSize: string;
|
||||
size: string;
|
||||
key: string;
|
||||
axis: string;
|
||||
client: string;
|
||||
direction: string;
|
||||
}
|
||||
export interface BarMap {
|
||||
vertical: BarMapItem;
|
||||
horizontal: BarMapItem;
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
export const BAR_MAP = {
|
||||
import { BarMap } from './types';
|
||||
export const BAR_MAP: BarMap = {
|
||||
vertical: {
|
||||
offset: 'offsetHeight',
|
||||
scroll: 'scrollTop',
|
||||
|
|
@ -22,7 +23,7 @@ export const BAR_MAP = {
|
|||
};
|
||||
|
||||
export function renderThumbStyle({ move, size, bar }) {
|
||||
const style = {};
|
||||
const style = {} as any;
|
||||
const translate = `translate${bar.axis}(${move}%)`;
|
||||
|
||||
style[bar.size] = size;
|
||||
|
|
@ -32,32 +33,13 @@ export function renderThumbStyle({ move, size, bar }) {
|
|||
|
||||
return style;
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
export const on = (function () {
|
||||
return function (element, event, handler) {
|
||||
if (element && event && handler) {
|
||||
element.addEventListener(event, handler, false);
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
/* istanbul ignore next */
|
||||
export const off = (function () {
|
||||
return function (element, event, handler) {
|
||||
if (element && event) {
|
||||
element.removeEventListener(event, handler, false);
|
||||
}
|
||||
};
|
||||
})();
|
||||
function extend(to, _from) {
|
||||
for (const key in _from) {
|
||||
to[key] = _from[key];
|
||||
}
|
||||
return to;
|
||||
function extend<T, K>(to: T, _from: K): T & K {
|
||||
return Object.assign(to, _from);
|
||||
}
|
||||
|
||||
export function toObject(arr) {
|
||||
var res = {};
|
||||
export function toObject<T>(arr: Array<T>): Record<string, T> {
|
||||
const res = {};
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (arr[i]) {
|
||||
extend(res, arr[i]);
|
||||
|
|
@ -278,7 +278,7 @@
|
|||
compact: true,
|
||||
};
|
||||
return (
|
||||
<div class={prefixCls}>
|
||||
<div class={[prefixCls, useSearchForm && 'table-form-container']}>
|
||||
{useSearchForm && (
|
||||
<BasicForm
|
||||
submitButtonOptions={{ loading: unref(loadingRef) }}
|
||||
|
|
@ -446,4 +446,31 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-form-container {
|
||||
& > div {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.ant-form {
|
||||
padding: 12px 12px 4px 12px;
|
||||
margin-bottom: 12px;
|
||||
background: #fff;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.ant-table-wrapper {
|
||||
padding: 12px;
|
||||
background: #fff;
|
||||
border-radius: 2px;
|
||||
|
||||
.ant-table-title {
|
||||
padding: 0 0 10px 0 !important;
|
||||
}
|
||||
|
||||
.ant-table.ant-table-bordered .ant-table-title {
|
||||
border: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
.table-form-container {
|
||||
.ant-form {
|
||||
padding: 12px 12px 4px 12px;
|
||||
margin-bottom: 12px;
|
||||
background: #fff;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.ant-table-wrapper {
|
||||
padding: 12px;
|
||||
background: #fff;
|
||||
border-radius: 2px;
|
||||
|
||||
.ant-table-title {
|
||||
padding: 0 0 10px 0 !important;
|
||||
}
|
||||
|
||||
.ant-table.ant-table-bordered .ant-table-title {
|
||||
border: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
@import './scrollbar.less';
|
||||
@import './formTable.less';
|
||||
|
||||
// 标题样式
|
||||
.common-title {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import VueCompositionAPI, { VueConstructor } from 'compatible-vue';
|
|||
|
||||
import setupSvgIcon from '@/assets/icons/setupSvgIcon';
|
||||
|
||||
import Scrollbar from '@/components/scrollbar';
|
||||
// import Scrollbar from '@/components/scrollbar';
|
||||
|
||||
import '@/setup/ant-design-vue/index';
|
||||
import '@/setup/ant-design-vue/spin';
|
||||
|
|
@ -16,7 +16,7 @@ export default {
|
|||
Vue.use(VueCompositionAPI);
|
||||
// Vue.use(RunTimeVue);
|
||||
|
||||
Vue.use(Scrollbar);
|
||||
// Vue.use(Scrollbar);
|
||||
// 使用svg sprit
|
||||
setupSvgIcon();
|
||||
// error handler
|
||||
|
|
|
|||
|
|
@ -129,6 +129,12 @@ class Tab extends VuexModule implements TabState {
|
|||
this.keepAliveTabsState = names as string[];
|
||||
}
|
||||
|
||||
@Mutation
|
||||
commitResetState(): void {
|
||||
this.tabsState = [];
|
||||
this.keepAliveTabsState = [];
|
||||
}
|
||||
|
||||
@Action({ rawError: true })
|
||||
closeLeftTabAction(route: RouteEx | TabItem): void {
|
||||
const index = this.tabsState.findIndex((item) => item.path === route.path);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import { routerInstance } from '@/router/index';
|
|||
import { useMessage } from '@/hooks/core/useMessage';
|
||||
|
||||
import { permissionStore } from './permission';
|
||||
import { tabStore } from './tab';
|
||||
|
||||
import { loginApi, getUserInfoById } from '@/api/sys/user';
|
||||
import {
|
||||
|
|
@ -111,6 +112,7 @@ class User extends VuexModule implements UserState {
|
|||
clearSession();
|
||||
permissionStore.commitHasRouteState(false);
|
||||
permissionStore.commitReset();
|
||||
tabStore.commitResetState();
|
||||
this.resetState();
|
||||
goLogin && getRouteInstance && getRouteInstance().replace(pageEnum.BASE_LOGIN);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,3 +38,7 @@ declare type CustomizedHTMLElement<T> = HTMLElement & T;
|
|||
declare type Indexable<T> = {
|
||||
[key: string]: T;
|
||||
};
|
||||
|
||||
declare type KeyString<T = any> = {
|
||||
[key: string]: T;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -127,3 +127,25 @@ export function hackCss(attr: string, value: string) {
|
|||
[attr]: value,
|
||||
};
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export const on = function (
|
||||
element: HTMLElement | Document | Window,
|
||||
event: string,
|
||||
handler: EventListenerOrEventListenerObject
|
||||
): void {
|
||||
if (element && event && handler) {
|
||||
element.addEventListener(event, handler, false);
|
||||
}
|
||||
};
|
||||
|
||||
/* istanbul ignore next */
|
||||
export const off = function (
|
||||
element: HTMLElement | Document | Window,
|
||||
event: string,
|
||||
handler: EventListenerOrEventListenerObject
|
||||
): void {
|
||||
if (element && event && handler) {
|
||||
element.removeEventListener(event, handler, false);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { Vue } from 'compatible-vue';
|
||||
import { isWindow } from './is/index';
|
||||
|
||||
let scrollBarWidth;
|
||||
let scrollBarWidth: number;
|
||||
|
||||
export default function () {
|
||||
if (Vue.prototype.$isServer) return 0;
|
||||
export default function (): number {
|
||||
if (!isWindow) return 0;
|
||||
if (scrollBarWidth !== undefined) return scrollBarWidth;
|
||||
|
||||
const outer = document.createElement('div');
|
||||
|
|
@ -22,9 +22,8 @@ export default function () {
|
|||
outer.appendChild(inner);
|
||||
|
||||
const widthWithScroll = inner.offsetWidth;
|
||||
if (outer && outer.parentNode) {
|
||||
outer.parentNode.removeChild(outer);
|
||||
}
|
||||
const parentNode = outer.parentNode;
|
||||
parentNode && parentNode.removeChild(outer);
|
||||
scrollBarWidth = widthNoScroll - widthWithScroll;
|
||||
|
||||
return scrollBarWidth;
|
||||
|
|
@ -41,7 +41,7 @@
|
|||
setup() {
|
||||
const [register] = useTable();
|
||||
return () => (
|
||||
<div class="table-form-container">
|
||||
<div>
|
||||
<div>
|
||||
<BasicTable
|
||||
useSearchForm
|
||||
|
|
|
|||
Loading…
Reference in New Issue