Merge pull request #193 from LostElkByte/master
refactor(index.html、demo、components、package.json): ♻️ 新增滚动公告组件、拖动demo、首屏加载样式优化
This commit is contained in:
73
index.html
73
index.html
@@ -17,7 +17,13 @@
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<div class="loader"></div>
|
<div class="loading-container">
|
||||||
|
<div class="loading-spinner">
|
||||||
|
<div class="loading-bar"></div>
|
||||||
|
<div class="loading-bar"></div>
|
||||||
|
<div class="loading-bar"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
@@ -34,27 +40,56 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loader {
|
.loading-container {
|
||||||
--d: 22px;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
width: 4px;
|
gap: 24px;
|
||||||
height: 4px;
|
align-items: center;
|
||||||
color: #25b09b;
|
justify-content: center;
|
||||||
border-radius: 50%;
|
|
||||||
box-shadow:
|
|
||||||
calc(1 * var(--d)) calc(0 * var(--d)) 0 0,
|
|
||||||
calc(0.707 * var(--d)) calc(0.707 * var(--d)) 0 1px,
|
|
||||||
calc(0 * var(--d)) calc(1 * var(--d)) 0 2px,
|
|
||||||
calc(-0.707 * var(--d)) calc(0.707 * var(--d)) 0 3px,
|
|
||||||
calc(-1 * var(--d)) calc(0 * var(--d)) 0 4px,
|
|
||||||
calc(-0.707 * var(--d)) calc(-0.707 * var(--d)) 0 5px,
|
|
||||||
calc(0 * var(--d)) calc(-1 * var(--d)) 0 6px;
|
|
||||||
animation: l27 1s infinite steps(8);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes l27 {
|
.loading-spinner {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-bar {
|
||||||
|
width: 4px;
|
||||||
|
height: 24px;
|
||||||
|
background-color: #498cff;
|
||||||
|
border-radius: 2px;
|
||||||
|
animation: loading-animation 1.2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-bar:nth-child(1) {
|
||||||
|
animation-delay: 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-bar:nth-child(2) {
|
||||||
|
animation-delay: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-bar:nth-child(3) {
|
||||||
|
animation-delay: 0.4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loading-animation {
|
||||||
|
0% {
|
||||||
|
opacity: 0.3;
|
||||||
|
transform: scaleY(0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scaleY(1.2);
|
||||||
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
transform: rotate(1turn);
|
opacity: 0.3;
|
||||||
|
transform: scaleY(0.5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -65,6 +65,7 @@
|
|||||||
"qs": "^6.14.0",
|
"qs": "^6.14.0",
|
||||||
"sortablejs": "^1.15.6",
|
"sortablejs": "^1.15.6",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
|
"vue-draggable-plus": "^0.6.0",
|
||||||
"vue-i18n": "^11.1.2",
|
"vue-i18n": "^11.1.2",
|
||||||
"vue-router": "^4.5.0"
|
"vue-router": "^4.5.0"
|
||||||
},
|
},
|
||||||
|
|||||||
412
src/components/TextScroll/index.vue
Normal file
412
src/components/TextScroll/index.vue
Normal file
@@ -0,0 +1,412 @@
|
|||||||
|
<!--
|
||||||
|
TextScroll 组件 - 文本滚动公告
|
||||||
|
|
||||||
|
功能:
|
||||||
|
- 支持水平方向文本滚动
|
||||||
|
- 提供多种预设样式(默认、成功、警告、危险、信息)
|
||||||
|
- 支持自定义滚动速度和方向
|
||||||
|
- 可选的打字机输入效果
|
||||||
|
- 鼠标悬停时暂停滚动
|
||||||
|
- 可选的关闭按钮
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
ref="containerRef"
|
||||||
|
class="text-scroll-container"
|
||||||
|
:class="[`text-scroll--${props.type}`]"
|
||||||
|
:typewriter="props.typewriter ? 'true' : undefined"
|
||||||
|
>
|
||||||
|
<!-- 左侧图标 -->
|
||||||
|
<div class="left-icon">
|
||||||
|
<el-icon><Bell /></el-icon>
|
||||||
|
</div>
|
||||||
|
<!-- 滚动内容包装器 -->
|
||||||
|
<div class="scroll-wrapper">
|
||||||
|
<div
|
||||||
|
ref="scrollContent"
|
||||||
|
class="text-scroll-content"
|
||||||
|
:class="{ scrolling: shouldScroll }"
|
||||||
|
:style="scrollStyle"
|
||||||
|
>
|
||||||
|
<!-- 滚动内容,复制两份以实现无缝滚动 -->
|
||||||
|
<div class="scroll-item" v-html="sanitizedContent" />
|
||||||
|
<div class="scroll-item" v-html="sanitizedContent" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 可选的关闭按钮 -->
|
||||||
|
<div v-if="showClose" class="right-icon" @click="handleRightIconClick">
|
||||||
|
<el-icon><Close /></el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useElementHover } from "@vueuse/core";
|
||||||
|
|
||||||
|
const emit = defineEmits(["close"]);
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
/** 滚动文本内容(必填) */
|
||||||
|
text: string;
|
||||||
|
/** 滚动速度,数值越小滚动越慢 */
|
||||||
|
speed?: number;
|
||||||
|
/** 滚动方向:左侧或右侧 */
|
||||||
|
direction?: "left" | "right";
|
||||||
|
/** 样式类型 */
|
||||||
|
type?: "default" | "success" | "warning" | "danger" | "info";
|
||||||
|
/** 是否显示关闭按钮 */
|
||||||
|
showClose?: boolean;
|
||||||
|
/** 是否启用打字机效果 */
|
||||||
|
typewriter?: boolean;
|
||||||
|
/** 打字机效果的速度,数值越小打字越快 */
|
||||||
|
typewriterSpeed?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义组件属性及默认值
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
speed: 70,
|
||||||
|
direction: "left",
|
||||||
|
type: "default",
|
||||||
|
showClose: false,
|
||||||
|
typewriter: false,
|
||||||
|
typewriterSpeed: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 容器元素引用
|
||||||
|
const containerRef = ref<HTMLElement | null>(null);
|
||||||
|
// 使用 vueuse 的 useElementHover 检测鼠标悬停状态
|
||||||
|
const isHovered = useElementHover(containerRef);
|
||||||
|
// 滚动内容元素引用
|
||||||
|
const scrollContent = ref<HTMLElement | null>(null);
|
||||||
|
// 动画持续时间(秒)
|
||||||
|
const animationDuration = ref(0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打字机效果相关状态
|
||||||
|
*/
|
||||||
|
// 当前已显示的文本内容
|
||||||
|
const currentText = ref("");
|
||||||
|
// 打字机定时器引用,用于清理
|
||||||
|
let typewriterTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
// 打字机效果是否已完成
|
||||||
|
const isTypewriterComplete = ref(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算是否应该滚动
|
||||||
|
* 条件:
|
||||||
|
* 1. 鼠标未悬停在组件上
|
||||||
|
* 2. 如果启用了打字机效果,则需要等待打字效果完成
|
||||||
|
*/
|
||||||
|
const shouldScroll = computed(() => {
|
||||||
|
if (props.typewriter) {
|
||||||
|
return !isHovered.value && isTypewriterComplete.value;
|
||||||
|
}
|
||||||
|
return !isHovered.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算最终显示的内容
|
||||||
|
* 如果启用了打字机效果,则显示当前已打出的文本
|
||||||
|
* 否则直接显示完整文本
|
||||||
|
* 注意:内容支持 HTML,使用时需注意 XSS 风险
|
||||||
|
*/
|
||||||
|
const sanitizedContent = computed(() => (props.typewriter ? currentText.value : props.text));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算滚动样式
|
||||||
|
* 包括动画持续时间、播放状态和方向
|
||||||
|
* 这些值通过 CSS 变量传递给样式
|
||||||
|
*/
|
||||||
|
const scrollStyle = computed(() => ({
|
||||||
|
"--animation-duration": `${animationDuration.value}s`,
|
||||||
|
"--animation-play-state": shouldScroll.value ? "running" : "paused",
|
||||||
|
"--animation-direction": props.direction === "left" ? "normal" : "reverse",
|
||||||
|
}));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算动画持续时间
|
||||||
|
* 根据内容宽度和设定的速度计算出合适的动画持续时间
|
||||||
|
* 内容越长或速度值越小,动画持续时间越长
|
||||||
|
*/
|
||||||
|
const calculateDuration = () => {
|
||||||
|
if (scrollContent.value) {
|
||||||
|
const contentWidth = scrollContent.value.scrollWidth / 2;
|
||||||
|
animationDuration.value = contentWidth / props.speed;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理关闭按钮点击事件
|
||||||
|
* 触发 close 事件,并直接销毁当前组件
|
||||||
|
*/
|
||||||
|
const handleRightIconClick = () => {
|
||||||
|
emit("close");
|
||||||
|
// 获取当前组件的DOM元素
|
||||||
|
if (containerRef.value) {
|
||||||
|
// 从DOM中移除元素
|
||||||
|
containerRef.value.remove();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动打字机效果
|
||||||
|
* 逐字显示文本内容,完成后设置状态以开始滚动
|
||||||
|
*/
|
||||||
|
const startTypewriter = () => {
|
||||||
|
let index = 0;
|
||||||
|
currentText.value = "";
|
||||||
|
isTypewriterComplete.value = false; // 重置状态
|
||||||
|
|
||||||
|
// 递归函数,逐字添加文本
|
||||||
|
const type = () => {
|
||||||
|
if (index < props.text.length) {
|
||||||
|
// 添加一个字符
|
||||||
|
currentText.value += props.text[index];
|
||||||
|
index++;
|
||||||
|
// 设置下一个字符的延迟
|
||||||
|
typewriterTimer = setTimeout(type, props.typewriterSpeed);
|
||||||
|
} else {
|
||||||
|
// 所有字符都已添加,设置完成状态
|
||||||
|
isTypewriterComplete.value = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 开始打字过程
|
||||||
|
type();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 计算初始动画持续时间
|
||||||
|
calculateDuration();
|
||||||
|
// 监听窗口大小变化,重新计算动画持续时间
|
||||||
|
window.addEventListener("resize", calculateDuration);
|
||||||
|
|
||||||
|
// 如果启用了打字机效果,开始打字
|
||||||
|
if (props.typewriter) {
|
||||||
|
startTypewriter();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
// 移除事件监听
|
||||||
|
window.removeEventListener("resize", calculateDuration);
|
||||||
|
// 清除打字机定时器
|
||||||
|
if (typewriterTimer) {
|
||||||
|
clearTimeout(typewriterTimer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听文本内容变化
|
||||||
|
* 当文本内容变化时,如果启用了打字机效果,重新开始打字
|
||||||
|
*/
|
||||||
|
watch(
|
||||||
|
() => props.text,
|
||||||
|
() => {
|
||||||
|
if (props.typewriter) {
|
||||||
|
// 清除现有定时器
|
||||||
|
if (typewriterTimer) {
|
||||||
|
clearTimeout(typewriterTimer);
|
||||||
|
}
|
||||||
|
// 重新开始打字效果
|
||||||
|
startTypewriter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.text-scroll-container {
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
padding-right: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: var(--el-color-primary-light-9) !important;
|
||||||
|
border: 1px solid var(--main-color);
|
||||||
|
border-radius: calc(var(--custom-radius) / 2 + 2px) !important;
|
||||||
|
|
||||||
|
.left-icon,
|
||||||
|
.right-icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 2;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 40px;
|
||||||
|
height: 100%;
|
||||||
|
text-align: center;
|
||||||
|
background-color: var(--el-color-primary-light-9) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-icon {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-icon {
|
||||||
|
right: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 34px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-scroll-content {
|
||||||
|
display: flex;
|
||||||
|
height: 34px;
|
||||||
|
line-height: 34px;
|
||||||
|
white-space: nowrap;
|
||||||
|
animation: scroll linear infinite;
|
||||||
|
animation-duration: var(--animation-duration);
|
||||||
|
animation-direction: var(--animation-direction);
|
||||||
|
animation-play-state: var(--animation-play-state);
|
||||||
|
|
||||||
|
.scroll-item {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 100%;
|
||||||
|
padding: 0 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--el-color-primary-light-2) !important;
|
||||||
|
text-align: left;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
:deep(a) {
|
||||||
|
color: #fd4e4e !important;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes scroll {
|
||||||
|
0% {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加类型样式
|
||||||
|
&.text-scroll--default {
|
||||||
|
background-color: var(--el-color-primary-light-9) !important;
|
||||||
|
border-color: var(--el-color-primary);
|
||||||
|
|
||||||
|
.right-icon,
|
||||||
|
.left-icon i {
|
||||||
|
color: var(--el-color-primary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-item {
|
||||||
|
color: var(--el-color-primary) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.text-scroll--success {
|
||||||
|
background-color: var(--el-color-success-light-9) !important;
|
||||||
|
border-color: var(--el-color-success);
|
||||||
|
|
||||||
|
.left-icon {
|
||||||
|
background-color: var(--el-color-success-light-9) !important;
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: var(--el-color-success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-item {
|
||||||
|
color: var(--el-color-success) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.text-scroll--warning {
|
||||||
|
background-color: var(--el-color-warning-light-9) !important;
|
||||||
|
border-color: var(--el-color-warning);
|
||||||
|
|
||||||
|
.left-icon {
|
||||||
|
background-color: var(--el-color-warning-light-9) !important;
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: var(--el-color-warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-item {
|
||||||
|
color: var(--el-color-warning) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.text-scroll--danger {
|
||||||
|
background-color: var(--el-color-danger-light-9) !important;
|
||||||
|
border-color: var(--el-color-danger);
|
||||||
|
|
||||||
|
.left-icon {
|
||||||
|
background-color: var(--el-color-danger-light-9) !important;
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: var(--el-color-danger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-item {
|
||||||
|
color: var(--el-color-danger) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.text-scroll--info {
|
||||||
|
background-color: var(--el-color-info-light-9) !important;
|
||||||
|
border-color: var(--el-color-info);
|
||||||
|
|
||||||
|
.left-icon {
|
||||||
|
background-color: var(--el-color-info-light-9) !important;
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: var(--el-color-info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-item {
|
||||||
|
color: var(--el-color-info) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加打字机效果的光标样式
|
||||||
|
.text-scroll-content .scroll-item {
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
opacity: 0;
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 仅在启用打字机效果时显示光标
|
||||||
|
.text-scroll-container[typewriter] .text-scroll-content .scroll-item::after {
|
||||||
|
content: "|";
|
||||||
|
opacity: 0;
|
||||||
|
animation: cursor 1s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes cursor {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
123
src/views/demo/drag.vue
Normal file
123
src/views/demo/drag.vue
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<el-row :gutter="24">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-card shadow="never">
|
||||||
|
<template #header><span class="card-header">基础示例</span></template>
|
||||||
|
<VueDraggable ref="el" v-model="userList" class="drag-container">
|
||||||
|
<div v-for="item in userList" :key="item.name" class="drag-item">
|
||||||
|
{{ item.name }}
|
||||||
|
</div>
|
||||||
|
</VueDraggable>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-card shadow="never">
|
||||||
|
<template #header><span class="card-header">过渡动画</span></template>
|
||||||
|
<VueDraggable
|
||||||
|
v-model="userList"
|
||||||
|
target=".sort-target"
|
||||||
|
:scroll="true"
|
||||||
|
class="drag-container"
|
||||||
|
>
|
||||||
|
<TransitionGroup type="transition" tag="ul" name="fade" class="sort-target">
|
||||||
|
<li v-for="item in userList" :key="item.name" class="drag-item">
|
||||||
|
{{ item.name }}
|
||||||
|
</li>
|
||||||
|
</TransitionGroup>
|
||||||
|
</VueDraggable>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-card shadow="never">
|
||||||
|
<template #header><span class="card-header">表格拖拽排序</span></template>
|
||||||
|
<VueDraggable v-model="userList" target="tbody" :animation="150">
|
||||||
|
<el-table :data="userList" row-key="name">
|
||||||
|
<el-table-column label="姓名" prop="name" />
|
||||||
|
<el-table-column label="角色" prop="roles" />
|
||||||
|
</el-table>
|
||||||
|
</VueDraggable>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-card shadow="never">
|
||||||
|
<template #header><span class="card-header">指定元素拖拽排序</span></template>
|
||||||
|
<VueDraggable v-model="userList" target="tbody" handle=".handle" :animation="150">
|
||||||
|
<el-table :data="userList" row-key="name">
|
||||||
|
<el-table-column label="姓名" prop="name" />
|
||||||
|
<el-table-column label="角色" prop="roles" />
|
||||||
|
<el-table-column label="操作" width="100">
|
||||||
|
<template #default>
|
||||||
|
<el-button size="default" class="handle">移动</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</VueDraggable>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { VueDraggable } from "vue-draggable-plus";
|
||||||
|
|
||||||
|
const userList = ref([
|
||||||
|
{ name: "路飞", roles: "船长·格斗家·D之一族" },
|
||||||
|
{ name: "索隆", roles: "剑豪·战斗员·三刀流大师" },
|
||||||
|
{ name: "娜美", roles: "航海士·气象学家·财务官" },
|
||||||
|
{ name: "山治", roles: "厨师·格斗家·黑足" },
|
||||||
|
{ name: "罗宾", roles: "考古学家·历史正文解读者" },
|
||||||
|
]);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.app-container {
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
.el-card {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-container {
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-item {
|
||||||
|
padding: 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
cursor: grab;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: transform 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-item:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 过渡动画 */
|
||||||
|
.fade-move,
|
||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: all 0.5s cubic-bezier(0.55, 0, 0.1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter-from,
|
||||||
|
.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scaleY(0.01) translate(20px, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-leave-active {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
30
src/views/demo/text-scroll.vue
Normal file
30
src/views/demo/text-scroll.vue
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<!-- 基础用法 -->
|
||||||
|
<TextScroll text="这是一条基础的滚动公告,默认向左滚动。" typewriter />
|
||||||
|
|
||||||
|
<!-- 使用不同的类型 -->
|
||||||
|
<TextScroll type="success" text="这是一条成功类型的滚动公告" typewriter />
|
||||||
|
|
||||||
|
<TextScroll type="warning" text="这是一条警告类型的滚动公告" />
|
||||||
|
|
||||||
|
<TextScroll type="danger" text="这是一条危险类型的滚动公告" />
|
||||||
|
|
||||||
|
<TextScroll type="info" text="这是一条信息类型的滚动公告" />
|
||||||
|
|
||||||
|
<!-- 自定义速度和方向 -->
|
||||||
|
<TextScroll text="这是一条速度较慢、向右滚动的公告" :speed="30" direction="right" showClose />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import TextScroll from "@/components/TextScroll/index.vue";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.app-container {
|
||||||
|
:deep(.text-scroll-container) {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user