项目创建

This commit is contained in:
浅念
2025-07-04 17:16:38 +08:00
parent ce47b39fa5
commit 719911918f
372 changed files with 105035 additions and 32 deletions
@@ -0,0 +1,293 @@
<!-- 文字滚动组件,支持5种样式类型,两种滚动方向,可自定义 HTML 内容 -->
<template>
<div ref="containerRef" class="text-scroll-container" :class="[`text-scroll--${props.type}`]">
<div class="left-icon">
<i class="iconfont-sys">&#xe64a;</i>
</div>
<div class="scroll-wrapper">
<div
class="text-scroll-content"
:class="{ scrolling: shouldScroll }"
:style="scrollStyle"
ref="scrollContent"
>
<div class="scroll-item" v-html="sanitizedContent"></div>
<div class="scroll-item" v-html="sanitizedContent"></div>
</div>
</div>
<div class="right-icon" @click="handleRightIconClick" v-if="showClose">
<i class="iconfont-sys">&#xe83a;</i>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
import { useElementHover } from '@vueuse/core'
defineOptions({ name: 'ArtTextScroll' })
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)
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)
// 修改滚动状态计算属性
const shouldScroll = computed(() => {
if (props.typewriter) {
return !isHovered.value && isTypewriterComplete.value
}
return !isHovered.value
})
// 修改 sanitizedContent 计算属性
const sanitizedContent = computed(() => (props.typewriter ? currentText.value : props.text))
// 修改 scrollStyle 计算属性
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
}
}
// 处理右图标点击事件
const handleRightIconClick = () => {
emit('close')
}
// 修改打字机效果实现
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-height: 34px;
$icon-width: 40px;
$border-radius: calc(var(--custom-radius) / 2 + 2px);
$types: (
default: primary,
success: success,
warning: warning,
danger: danger,
info: info
);
// 基础容器样式
.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);
border: 1px solid var(--main-color);
border-radius: $border-radius;
// 左右图标公共样式
.left-icon,
.right-icon {
position: absolute;
top: 0;
bottom: 0;
z-index: 2;
width: $icon-width;
height: $text-scroll-height;
line-height: $text-scroll-height;
text-align: center;
background-color: var(--el-color-primary-light-9);
i {
color: var(--main-color);
}
}
.left-icon {
left: 0;
}
.right-icon {
right: 0;
cursor: pointer;
background-color: transparent;
}
// 滚动内容包装器
.scroll-wrapper {
flex: 1;
margin-left: $text-scroll-height;
overflow: hidden;
}
// 滚动内容
.text-scroll-content {
display: flex;
height: $text-scroll-height;
line-height: $text-scroll-height;
white-space: nowrap;
animation: scroll linear infinite;
animation-duration: var(--animation-duration);
animation-play-state: var(--animation-play-state);
animation-direction: var(--animation-direction);
.scroll-item {
display: inline-block;
min-width: 100%;
padding: 0 10px;
font-size: 14px;
color: var(--el-color-primary-light-2);
text-align: center;
:deep(a) {
color: #fd4e4e;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
// 打字机光标效果
&::after {
content: '|';
opacity: 0;
animation: cursor 1s infinite;
}
}
}
// 动态生成类型样式
@each $type, $color in $types {
&.text-scroll--#{$type} {
background-color: var(--el-color-#{$color}-light-9);
border-color: var(--el-color-#{$color});
.left-icon,
.right-icon {
background-color: var(--el-color-#{$color}-light-9);
i {
color: var(--el-color-#{$color});
}
}
.scroll-item {
color: var(--el-color-#{$color});
}
}
}
}
// 滚动动画
@keyframes scroll {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-100%);
}
}
// 光标动画
@keyframes cursor {
0%,
100% {
opacity: 0;
}
50% {
opacity: 1;
}
}
</style>