feat: 新增疯言疯语功能并优化UI样式

- 添加疯言疯语服务及页面,支持随机字符颜色变化效果
- 引入汉仪唐韵字体并优化全局字体设置
- 重构日期工具函数,优化时间显示格式
- 改进左侧模块布局,添加文章/分类/标签统计
- 优化浮动按钮组件,增加动态过渡效果
- 调整多个页面的背景透明度,提升视觉一致性
- 完善文章保存页面样式和交互逻辑
- 更新关于页面内容,增加个人介绍和技术栈展示
- 修复路由状态管理问题,优化页面跳转逻辑
This commit is contained in:
qingfeng1121
2025-11-05 16:11:46 +08:00
parent a927ad5a4d
commit ad893b3e5c
19 changed files with 1226 additions and 600 deletions

View File

@@ -7,37 +7,210 @@
:key="item.id"
>
<div class="nonsense-meta-info">
<span class="nonsense-time">{{ item.time }}</span>
<span class="nonsense-time">{{ formatRelativeTime(item.time) }}</span>
</div>
<div class="nonsense-content">
<span
v-for="(char, index) in item.content.split('')"
:key="index"
:ref="el => setCharRef(el, item.id, index)"
:style="getCharStyle(item.id, index)"
>{{ char }}</span>
</div>
<div class="nonsense-content">{{ item.content }}</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { ref, onMounted, onBeforeUnmount } from 'vue'
import {nonsenseService } from '@/services'
import { ElMessage } from 'element-plus'
import { formatRelativeTime} from '@/utils/dateUtils'
/**
* 吐槽数据列表
* 仅站长可见/可发
*/
const nonsenseList = ref([
{
id: 1,
content: '嘿嘿 嘿嘿嘿流口水ing',
time: '2025-09-26 09:30'
const nonsenseList = ref([])
// 存储字符引用和样式的映射
const charRefs = ref(new Map())
const charStyles = ref(new Map())
// 定时器引用
let colorChangeTimer = null
/**
* 加载所有吐槽内容
*/
const loadNonsenseList = async () => {
try {
const response = await nonsenseService.getAllNonsense()
if (response.code === 200) {
nonsenseList.value = response.data
}else{
ElMessage.error('加载吐槽内容失败')
}
} catch (error) {
console.error('加载吐槽内容失败:', error)
} finally {
console.log('加载吐槽内容完成')
}
])
}
// 设置字符引用
const setCharRef = (el, itemId, index) => {
if (el) {
const key = `${itemId}_${index}`
charRefs.value.set(key, el)
}
}
// 获取字符样式
const getCharStyle = (itemId, index) => {
const key = `${itemId}_${index}`
return charStyles.value.get(key) || {}
}
// 生成随机颜色
const getRandomColor = () => {
const colors = ['#ff6b6b', '#4ecdc4', '#45b7d1', '#f9ca24', '#6c5ce7', '#e84393', '#00b894', '#fdcb6e']
return colors[Math.floor(Math.random() * colors.length)]
}
// 随机改变部分字体颜色并添加信号故障效果
const randomChangeColors = () => {
const keys = Array.from(charRefs.value.keys())
if (keys.length === 0) return
// 随机选择20-30%的字符改变颜色
const countToChange = Math.floor(keys.length * (Math.random() * 0.1 + 0.2))
const shuffledKeys = [...keys].sort(() => 0.5 - Math.random())
const selectedKeys = shuffledKeys.slice(0, countToChange)
// 创建信号故障效果
createSignalGlitchEffect(selectedKeys)
}
// 创建数字噪点效果
const createDigitalNoiseEffect = (selectedKeys) => {
// 噪点字符集
const noiseChars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+-=[]{}|;:,.<>?';
selectedKeys.forEach(key => {
const charElement = charRefs.value.get(key);
if (charElement) {
const rect = charElement.getBoundingClientRect();
const container = charElement.closest('.nonsense-item');
const containerRect = container.getBoundingClientRect();
// 生成3-5个噪点
const noiseCount = Math.floor(Math.random() * 3) + 3;
for (let i = 0; i < noiseCount; i++) {
const noiseEl = document.createElement('span');
noiseEl.textContent = noiseChars.charAt(Math.floor(Math.random() * noiseChars.length));
// 随机位置相对于原字符
const x = Math.random() * 30 - 20; // -20到20px
const y = Math.random() * 20 - 15; // -15到15px
// 随机大小和透明度
const size = Math.random() * 8 + 8; // 8-16px
const opacity = Math.random() * 0.6 + 0.2; // 0.2-0.8
noiseEl.style.cssText = `
position: absolute;
left: ${rect.left - containerRect.left + x}px;
top: ${rect.top - containerRect.top + y}px;
font-size: ${size}px;
opacity: ${opacity};
color: ${getRandomColor()};
pointer-events: none;
z-index: 1;
font-family: monospace;
transition: all 0.2s ease;
`;
container.appendChild(noiseEl);
// 噪点逐渐消失
setTimeout(() => {
noiseEl.style.opacity = '0';
setTimeout(() => {
if (container.contains(noiseEl)) {
container.removeChild(noiseEl);
}
}, 200);
}, Math.random() * 300 + 200); // 200-500ms后开始消失
}
}
});
};
// 创建信号故障效果
const createSignalGlitchEffect = (selectedKeys) => {
// 第一步:随机偏移字符位置,模拟信号干扰
selectedKeys.forEach(key => {
const glitchOffset = {
transform: `translate(${Math.random() * 6 - 3}px, ${Math.random() * 6 - 3}px)`,
opacity: Math.random() * 0.4 + 0.6, // 随机透明度
transition: 'all 0.1s ease'
}
charStyles.value.set(key, glitchOffset)
})
// 同时创建数字噪点效果
createDigitalNoiseEffect(selectedKeys);
// 第二步:快速恢复并闪烁
setTimeout(() => {
selectedKeys.forEach(key => {
const flashStyle = {
transform: 'translate(0, 0)',
opacity: Math.random() > 0.5 ? 0 : 1, // 闪烁效果
transition: 'all 0.05s ease'
}
charStyles.value.set(key, flashStyle)
})
// 第三步:最终设置新颜色
setTimeout(() => {
selectedKeys.forEach(key => {
const finalStyle = {
color: getRandomColor(),
opacity: 1,
transform: 'translate(0, 0)',
transition: 'all 0.3s ease'
}
charStyles.value.set(key, finalStyle)
})
}, 80)
}, 100)}
// 组件挂载时获取数据并启动定时器
onMounted(() => {
loadNonsenseList()
// 启动定时器
colorChangeTimer = setInterval(randomChangeColors, 1000)
})
// 组件卸载时清除定时器
onBeforeUnmount(() => {
if (colorChangeTimer) {
clearInterval(colorChangeTimer)
colorChangeTimer = null
}
})
</script>
<style scoped>
/* 吐槽页面主容器样式 */
.nonsense-container {
background: rgba(255,255,255,0.95);
/* background-color: rgba(255, 255, 255, 0.85); */
border-radius: 12px;
padding: 32px 20px 24px 20px;
box-shadow: 0 2px 12px rgba(0,0,0,0.06);
/* box-shadow: 0 2px 12px rgba(0,0,0,0.06); */
transition: box-shadow 0.3s ease;
}
@@ -74,7 +247,7 @@ const nonsenseList = ref([
/* 吐槽项样式 */
.nonsense-item {
background: #f8fafd;
background-color: rgba(255, 255, 255, 0.85);
border-radius: 10px;
padding: 18px 20px 14px 20px;
box-shadow: 0 1px 4px rgba(0,0,0,0.03);