feat:新增pure-admin前端

This commit is contained in:
chenchun
2024-08-23 14:31:00 +08:00
parent 556d32729a
commit 4bc2cebd60
579 changed files with 85268 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
import reSeamlessScroll from "./src/index.vue";
import { withInstall } from "@pureadmin/utils";
/** 无缝滚动组件 */
export const ReSeamlessScroll = withInstall(reSeamlessScroll);
export default ReSeamlessScroll;

View File

@@ -0,0 +1,538 @@
<script setup lang="ts">
import {
type PropType,
type CSSProperties,
ref,
unref,
nextTick,
computed
} from "vue";
import {
tryOnMounted,
tryOnUnmounted,
templateRef,
useDebounceFn
} from "@vueuse/core";
import * as utilsMethods from "./utils";
const { animationFrame, copyObj } = utilsMethods;
animationFrame();
defineOptions({
name: "ReSeamlessScroll"
});
const props = defineProps({
data: {
type: Array as PropType<unknown>
},
classOption: {
type: Object as PropType<unknown>
}
});
const emit = defineEmits<{
(e: "scrollEnd"): void;
}>();
const xPos = ref<number>(0);
const yPos = ref<number>(0);
const delay = ref<number>(0);
const height = ref<number>(0);
// 外容器宽度
const width = ref<number>(0);
// 内容实际宽度
const realBoxWidth = ref<number>(0);
const realBoxHeight = ref<number>(0);
const copyHtml = ref("");
// single 单步滚动的定时器
let singleWaitTime = null;
// move动画的animationFrame定时器
let reqFrame = null;
let startPos = null;
//记录touchStart时候的posY
let startPosY = null;
//记录touchStart时候的posX
let startPosX = null;
// mouseenter mouseleave 控制scrollMove()的开关
let isHover = false;
let ease = "ease-in";
if (props.classOption["key"] === undefined) {
// eslint-disable-next-line vue/no-mutating-props
props.classOption["key"] = 0;
}
const wrap = templateRef<HTMLElement | null>(
`wrap${props.classOption["key"]}`,
null
);
const slotList = templateRef<HTMLElement | null>(
`slotList${props.classOption["key"]}`,
null
);
const realBox = templateRef<HTMLElement | null>(
`realBox${props.classOption["key"]}`,
null
);
const leftSwitchState = computed(() => {
return unref(xPos) < 0;
});
const rightSwitchState = computed(() => {
return Math.abs(unref(xPos)) < unref(realBoxWidth) - unref(width);
});
const defaultOption = computed(() => {
return {
//步长
step: 1,
//启动无缝滚动最小数据数
limitMoveNum: 5,
//是否启用鼠标hover控制
hoverStop: true,
// bottom 往下 top 往上(默认) left 向左 right 向右
direction: "top",
//开启移动端touch
openTouch: true,
//单条数据高度有值hoverStop关闭
singleHeight: 0,
//单条数据宽度有值hoverStop关闭
singleWidth: 0,
//单步停止等待时间
waitTime: 1000,
switchOffset: 30,
autoPlay: true,
navigation: false,
switchSingleStep: 134,
switchDelay: 400,
switchDisabledClass: "disabled",
// singleWidth/singleHeight 是否开启rem度量
isSingleRemUnit: false
};
});
const options = computed(() => {
// @ts-expect-error
return copyObj({}, unref(defaultOption), props.classOption);
});
const leftSwitchClass = computed(() => {
return unref(leftSwitchState) ? "" : unref(options).switchDisabledClass;
});
const rightSwitchClass = computed(() => {
return unref(rightSwitchState) ? "" : unref(options).switchDisabledClass;
});
const leftSwitch = computed((): CSSProperties => {
return {
position: "absolute",
margin: `${unref(height) / 2}px 0 0 -${unref(options).switchOffset}px`,
transform: "translate(-100%,-50%)"
};
});
const rightSwitch = computed((): CSSProperties => {
return {
position: "absolute",
margin: `${unref(height) / 2}px 0 0 ${
unref(width) + unref(options).switchOffset
}px`,
transform: "translateY(-50%)"
};
});
const isHorizontal = computed(() => {
return (
unref(options).direction !== "bottom" && unref(options).direction !== "top"
);
});
const float = computed((): CSSProperties => {
return unref(isHorizontal)
? { float: "left", overflow: "hidden" }
: { overflow: "hidden" };
});
const pos = computed(() => {
return {
transform: `translate(${unref(xPos)}px,${unref(yPos)}px)`,
transition: `all ${ease} ${unref(delay)}ms`,
overflow: "hidden"
};
});
const navigation = computed(() => {
return unref(options).navigation;
});
const autoPlay = computed(() => {
if (unref(navigation)) return false;
return unref(options).autoPlay;
});
const scrollSwitch = computed(() => {
// 从 props 解构出来的 属性 不再具有响应性.
return (props.data as any).length >= unref(options).limitMoveNum;
});
const hoverStopSwitch = computed(() => {
return unref(options).hoverStop && unref(autoPlay) && unref(scrollSwitch);
});
const canTouchScroll = computed(() => {
return unref(options).openTouch;
});
const baseFontSize = computed(() => {
return unref(options).isSingleRemUnit
? parseInt(window.getComputedStyle(document.documentElement, null).fontSize)
: 1;
});
const realSingleStopWidth = computed(() => {
return unref(options).singleWidth * unref(baseFontSize);
});
const realSingleStopHeight = computed(() => {
return unref(options).singleHeight * unref(baseFontSize);
});
const step = computed(() => {
let singleStep;
const step = unref(options).step;
if (unref(isHorizontal)) {
singleStep = unref(realSingleStopWidth);
} else {
singleStep = unref(realSingleStopHeight);
}
if (singleStep > 0 && singleStep % step > 0) {
throw "如果设置了单步滚动step需是单步大小的约数否则无法保证单步滚动结束的位置是否准确";
}
return step;
});
function reset() {
xPos.value = 0;
yPos.value = 0;
scrollCancle();
scrollInitMove();
}
function leftSwitchClick() {
if (!unref(leftSwitchState)) return;
// 小于单步距离
if (Math.abs(unref(xPos)) < unref(options).switchSingleStep) {
xPos.value = 0;
return;
}
xPos.value += unref(options).switchSingleStep;
}
function rightSwitchClick() {
if (!unref(rightSwitchState)) return;
// 小于单步距离
if (
unref(realBoxWidth) - unref(width) + unref(xPos) <
unref(options).switchSingleStep
) {
xPos.value = unref(width) - unref(realBoxWidth);
return;
}
xPos.value -= unref(options).switchSingleStep;
}
function scrollCancle() {
cancelAnimationFrame(reqFrame || "");
}
function touchStart(e) {
if (!unref(canTouchScroll)) return;
let timer;
//touches数组对象获得屏幕上所有的touch取第一个touch
const touch = e.targetTouches[0];
const { waitTime, singleHeight, singleWidth } = unref(options);
//取第一个touch的坐标值
startPos = {
x: touch.pageX,
y: touch.pageY
};
//记录touchStart时候的posY
startPosY = unref(yPos);
//记录touchStart时候的posX
startPosX = unref(xPos);
if (!!singleHeight && !!singleWidth) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
scrollCancle();
}, waitTime + 20);
} else {
scrollCancle();
}
}
function touchMove(e) {
//当屏幕有多个touch或者页面被缩放过就不执行move操作
if (
!unref(canTouchScroll) ||
e.targetTouches.length > 1 ||
(e.scale && e.scale !== 1)
)
return;
const touch = e.targetTouches[0];
const { direction } = unref(options);
const endPos = {
x: touch.pageX - startPos.x,
y: touch.pageY - startPos.y
};
//阻止触摸事件的默认行为,即阻止滚屏
e.preventDefault();
//dir1表示纵向滑动0为横向滑动
const dir = Math.abs(endPos.x) < Math.abs(endPos.y) ? 1 : 0;
if (
(dir === 1 && direction === "bottom") ||
(dir === 1 && direction === "top")
) {
// 表示纵向滑动 && 运动方向为上下
yPos.value = startPosY + endPos.y;
} else if (
(dir === 0 && direction === "left") ||
(dir === 0 && direction === "right")
) {
// 为横向滑动 && 运动方向为左右
xPos.value = startPosX + endPos.x;
}
}
function touchEnd() {
if (!unref(canTouchScroll)) return;
let timer: any;
const direction = unref(options).direction;
delay.value = 50;
if (direction === "top") {
if (unref(yPos) > 0) yPos.value = 0;
} else if (direction === "bottom") {
const h = (unref(realBoxHeight) / 2) * -1;
if (unref(yPos) < h) yPos.value = h;
} else if (direction === "left") {
if (unref(xPos) > 0) xPos.value = 0;
} else if (direction === "right") {
const w = unref(realBoxWidth) * -1;
if (unref(xPos) < w) xPos.value = w;
}
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
delay.value = 0;
scrollMove();
}, unref(delay));
}
function enter() {
if (unref(hoverStopSwitch)) scrollStopMove();
}
function leave() {
if (unref(hoverStopSwitch)) scrollStartMove();
}
function scrollMove() {
// 鼠标移入时拦截scrollMove()
if (isHover) return;
//进入move立即先清除动画 防止频繁touchMove导致多动画同时进行
// scrollCancle();
reqFrame = requestAnimationFrame(function () {
//实际高度
const h = unref(realBoxHeight) / 2;
//宽度
const w = unref(realBoxWidth) / 2;
const { direction, waitTime } = unref(options);
if (direction === "top") {
// 上
if (Math.abs(unref(yPos)) >= h) {
emit("scrollEnd");
yPos.value = 0;
}
yPos.value -= step.value;
} else if (direction === "bottom") {
// 下
if (unref(yPos) >= 0) {
emit("scrollEnd");
yPos.value = h * -1;
}
yPos.value += step.value;
} else if (direction === "left") {
// 左
if (Math.abs(unref(xPos)) >= w) {
emit("scrollEnd");
xPos.value = 0;
}
xPos.value -= step.value;
} else if (direction === "right") {
// 右
if (unref(xPos) >= 0) {
emit("scrollEnd");
xPos.value = w * -1;
}
xPos.value += step.value;
}
if (singleWaitTime) clearTimeout(singleWaitTime);
if (unref(realSingleStopHeight)) {
//是否启动了单行暂停配置
if (Math.abs(unref(yPos)) % unref(realSingleStopHeight) < unref(step)) {
// 符合条件暂停waitTime
singleWaitTime = setTimeout(() => {
scrollMove();
}, waitTime);
} else {
scrollMove();
}
} else if (unref(realSingleStopWidth)) {
if (Math.abs(unref(xPos)) % unref(realSingleStopWidth) < unref(step)) {
// 符合条件暂停waitTime
singleWaitTime = setTimeout(() => {
scrollMove();
}, waitTime);
} else {
scrollMove();
}
} else {
scrollMove();
}
});
}
function scrollInitMove() {
nextTick(() => {
const { switchDelay } = unref(options);
//清空copy
copyHtml.value = "";
if (unref(isHorizontal)) {
height.value = unref(wrap).offsetHeight;
width.value = unref(wrap).offsetWidth;
let slotListWidth = unref(slotList).offsetWidth;
// 水平滚动设置warp width
if (unref(autoPlay)) {
// 修正offsetWidth四舍五入
slotListWidth = slotListWidth * 2 + 1;
}
unref(realBox).style.width = slotListWidth + "px";
realBoxWidth.value = slotListWidth;
}
if (unref(autoPlay)) {
ease = "ease-in";
delay.value = 0;
} else {
ease = "linear";
delay.value = switchDelay;
return;
}
// 是否可以滚动判断
if (unref(scrollSwitch)) {
let timer;
if (timer) clearTimeout(timer);
copyHtml.value = unref(slotList).innerHTML;
setTimeout(() => {
realBoxHeight.value = unref(realBox)?.offsetHeight;
scrollMove();
}, 0);
} else {
scrollCancle();
yPos.value = xPos.value = 0;
}
});
}
function scrollStartMove() {
//开启scrollMove
isHover = false;
scrollMove();
}
function scrollStopMove() {
//关闭scrollMove
isHover = true;
// 防止频频hover进出单步滚动,导致定时器乱掉
if (singleWaitTime) clearTimeout(singleWaitTime);
scrollCancle();
}
// 鼠标滚轮事件
function wheel(e) {
if (
unref(options).direction === "left" ||
unref(options).direction === "right"
)
return;
useDebounceFn(() => {
e.deltaY > 0 ? (yPos.value -= step.value) : (yPos.value += step.value);
}, 50)();
}
// watchEffect(() => {
// const watchData = data;
// if (!watchData) return;
// nextTick(() => {
// reset();
// });
// const watchAutoPlay = unref(autoPlay);
// if (watchAutoPlay) {
// reset();
// } else {
// scrollStopMove();
// }
// });
tryOnMounted(() => {
scrollInitMove();
});
tryOnUnmounted(() => {
scrollCancle();
clearTimeout(singleWaitTime);
});
defineExpose({
reset
});
</script>
<template>
<div :ref="'wrap' + classOption['key']">
<div
v-if="navigation"
:style="leftSwitch"
:class="leftSwitchClass"
@click="leftSwitchClick"
>
<slot name="left-switch" />
</div>
<div
v-if="navigation"
:style="rightSwitch"
:class="rightSwitchClass"
@click="rightSwitchClick"
>
<slot name="right-switch" />
</div>
<div
:ref="'realBox' + classOption['key']"
:style="pos"
@mouseenter="enter"
@mouseleave="leave"
@touchstart.passive="touchStart"
@touchmove.passive="touchMove"
@touchend="touchEnd"
@mousewheel.passive="wheel"
>
<div :ref="'slotList' + classOption['key']" :style="float">
<slot />
</div>
<div :style="float" v-html="copyHtml" />
</div>
</div>
</template>

View File

@@ -0,0 +1,115 @@
/**
* @desc AnimationFrame简单兼容hack
*/
export const animationFrame = () => {
window.cancelAnimationFrame = (() => {
return (
window.cancelAnimationFrame ||
window.webkitCancelAnimationFrame ||
window.mozCancelAnimationFrame ||
window.oCancelAnimationFrame ||
window.msCancelAnimationFrame ||
function (id) {
return window.clearTimeout(id);
}
);
})();
window.requestAnimationFrame = (function () {
return (
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function (callback) {
return window.setTimeout(callback, 1000 / 60);
}
);
})();
};
/**
* @desc 判断数组是否相等
* @return {Boolean}
* @param arr1
* @param arr2
*/
export const arrayEqual = (arr1: Array<any>, arr2: Array<any>) => {
if (arr1 === arr2) return true;
if (arr1.length !== arr2.length) return false;
for (let i = 0; i < arr1.length; ++i) {
if (arr1[i] !== arr2[i]) return false;
}
return true;
};
/**
* @desc 深浅合并拷贝
*/
export function copyObj() {
if (!Array.isArray) {
// @ts-expect-error
Array.isArray = function (arg) {
return Object.prototype.toString.call(arg) === "[object Array]";
};
}
let name,
options,
src,
copy,
copyIsArray,
clone,
i = 1,
target = arguments[0] || {}, // 使用||运算符排除隐式强制类型转换为false的数据类型
deep = false,
len = arguments.length;
if (typeof target === "boolean") {
deep = target;
target = arguments[1] || {};
i++;
}
if (typeof target !== "object" && typeof target !== "function") {
target = {};
}
// 如果arguments.length === 1 或typeof arguments[0] === 'boolean',且存在arguments[1]则直接返回target对象
if (i === len) {
return target;
}
for (; i < len; i++) {
//所以如果源对象中数据类型为Undefined或Null那么就会跳过本次循环接着循环下一个源对象
if ((options = arguments[i]) != null) {
// 如果遇到源对象的数据类型为Boolean, Number for in循环会被跳过不执行for in循环// src用于判断target对象是否存在name属性
for (name in options) {
// src用于判断target对象是否存在name属性
src = target[name];
// 需要复制的属性当前源对象的name属性
copy = options[name];
// 判断copy是否是数组
copyIsArray = Array.isArray(copy);
// 如果是深复制且copy是一个对象或数组则需要递归直到copy成为一个基本数据类型为止
if (deep && copy && (typeof copy === "object" || copyIsArray)) {
if (copyIsArray) {
copyIsArray = false;
// 如果目标对象存在name属性且是一个数组
// 则使用目标对象的name属性否则重新创建一个数组用于复制
clone = src && Array.isArray(src) ? src : [];
} else {
// 如果目标对象存在name属性且是一个对象则使用目标对象的name属性否则重新创建一个对象用于复制
clone = src && typeof src === "object" ? src : {};
}
// 深复制所以递归调用copyObject函数
// 返回值为target对象即clone对象
// copy是一个源对象
// @ts-expect-error
target[name] = copyObj(deep, clone, copy);
} else if (copy !== undefined) {
// 浅复制直接复制到target对象上
target[name] = copy;
}
}
}
}
return target;
}