新增文章状态管理功能,支持草稿、已发表和已删除状态的显示与切换 重构分类和标签展示模块,添加点击跳转功能 优化文章列表页面,增加状态筛选和分页功能 完善疯言疯语模块,支持编辑和删除操作 修复路由跳转和页面刷新问题
369 lines
9.8 KiB
Vue
369 lines
9.8 KiB
Vue
<template>
|
|
<div class="nonsense-container">
|
|
<div class="nonsense-list">
|
|
<div class="nonsense-item" v-for="item in nonsenseList" :key="item.id">
|
|
<div class="nonsense-meta-info">
|
|
<span class="nonsense-time">{{ formatRelativeTime(item.time) }}</span>
|
|
<div class="article-status-badge-container" v-if="globalStore.Login">
|
|
<span v-if="item.status === 0" class="article-status-badge badge badge-warning">未发表</span>
|
|
<span v-if="item.status === 1" class="article-status-badge badge badge-success">已发表</span>
|
|
<span v-if="item.status === 2" class="article-status-badge badge badge-danger">已删除</span>
|
|
<span class="edit-button" @click="handleEdit(item)">编辑</span>
|
|
<span class="delete-button" @click="handleDelete(item.id)">删除</span>
|
|
</div>
|
|
</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>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
|
import { nonsenseService } from '@/services'
|
|
import { ElMessage } from 'element-plus'
|
|
import { formatRelativeTime } from '@/utils/dateUtils'
|
|
import { useGlobalStore } from '@/store/globalStore'
|
|
const globalStore = useGlobalStore()
|
|
/**
|
|
* 吐槽数据列表
|
|
* 仅站长可见/可发
|
|
*/
|
|
const nonsenseList = ref([])
|
|
|
|
// 存储字符引用和样式的映射
|
|
const charRefs = ref(new Map())
|
|
const charStyles = ref(new Map())
|
|
|
|
// 定时器引用
|
|
let colorChangeTimer = null
|
|
|
|
/**
|
|
* 加载所有吐槽内容
|
|
*/
|
|
const loadNonsenseList = async () => {
|
|
try {
|
|
const response = await nonsenseService.getNonsenseByStatus(1)
|
|
if (response.code === 200) {
|
|
nonsenseList.value = response.data
|
|
} else {
|
|
ElMessage.error('加载吐槽内容失败')
|
|
}
|
|
} catch (error) {
|
|
console.error('加载吐槽内容失败:', error)
|
|
} finally {
|
|
console.log('加载吐槽内容完成')
|
|
}
|
|
}
|
|
// 编辑吐槽内容
|
|
const handleEdit = (item) => {
|
|
// 清除更新文章状态
|
|
|
|
ElMessageBox.prompt('请输入您的疯言疯语:', '发表疯言疯语', {
|
|
confirmButtonText: '保存',
|
|
cancelButtonText: '取消',
|
|
inputValue: item.content,
|
|
inputType: 'textarea',
|
|
inputRows: 4,
|
|
showCancelButton: true
|
|
}).then(({ value }) => {
|
|
// 保存疯言疯语
|
|
updateNonsense(value, item.id)
|
|
}).catch(() => {
|
|
// 取消操作,静默处理
|
|
})
|
|
}
|
|
// 更新吐槽内容
|
|
const updateNonsense = (content, id) => {
|
|
if (!content || content.trim() === '') {
|
|
ElMessage.warning('内容不能为空')
|
|
return
|
|
}
|
|
// 调用服务更新疯言疯语
|
|
nonsenseService.updateNonsense({
|
|
id,
|
|
content: content.trim(),
|
|
time: new Date(),
|
|
status: 1
|
|
}).then(response => {
|
|
if (response.code === 200) {
|
|
ElMessage.success('疯言疯语更新成功')
|
|
loadNonsenseList()
|
|
} else {
|
|
ElMessage.error(response.message || '更新失败')
|
|
}
|
|
}).catch(err => handleErrorResponse(err, '更新失败'))
|
|
}
|
|
// 删除吐槽内容
|
|
const handleDelete = async (id) => {
|
|
try {
|
|
// 确认删除
|
|
const confirm = window.confirm('确定删除吗?')
|
|
if (!confirm) return
|
|
const response = await nonsenseService.deleteNonsense(id)
|
|
if (response.code === 200) {
|
|
ElMessage.success('删除成功')
|
|
loadNonsenseList()
|
|
} else {
|
|
ElMessage.error('删除失败')
|
|
}
|
|
} catch (error) {
|
|
console.error('删除失败:', error)
|
|
}
|
|
}
|
|
// 设置字符引用
|
|
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-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); */
|
|
transition: box-shadow 0.3s ease;
|
|
}
|
|
|
|
/* 吐槽页面主容器悬浮效果 */
|
|
.nonsense-container:hover {
|
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
|
}
|
|
|
|
/* 吐槽头部样式 */
|
|
.nonsense-header {
|
|
text-align: center;
|
|
margin-bottom: 28px;
|
|
}
|
|
|
|
.nonsense-header h1 {
|
|
font-size: 2.2rem;
|
|
margin-bottom: 8px;
|
|
color: #409eff;
|
|
letter-spacing: 2px;
|
|
}
|
|
|
|
.nonsense-description {
|
|
color: #888;
|
|
font-size: 1rem;
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
/* 吐槽列表样式 */
|
|
.nonsense-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 18px;
|
|
}
|
|
|
|
/* 吐槽项样式 */
|
|
.nonsense-item {
|
|
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);
|
|
position: relative;
|
|
transition: box-shadow 0.2s, transform 0.2s ease;
|
|
}
|
|
|
|
/* 吐槽项悬浮效果 */
|
|
.nonsense-item:hover {
|
|
box-shadow: 0 4px 16px rgba(64, 158, 255, 0.12);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
/* 吐槽元信息样式 */
|
|
.nonsense-meta-info {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
align-items: center;
|
|
font-size: 13px;
|
|
color: #aaa;
|
|
margin-bottom: 8px;
|
|
text-align: right;
|
|
}
|
|
.nonsense-meta-info span {
|
|
padding: 4px 8px;
|
|
margin-right: 8px;
|
|
}
|
|
|
|
/* 吐槽内容样式 */
|
|
.nonsense-content {
|
|
font-size: 1.08rem;
|
|
color: #333;
|
|
line-height: 1.8;
|
|
word-break: break-all;
|
|
font-style: italic;
|
|
}
|
|
|
|
/* 响应式设计 */
|
|
@media (max-width: 768px) {
|
|
.nonsense-container {
|
|
padding: 14px 4px 10px 4px;
|
|
margin: 0 8px;
|
|
}
|
|
|
|
.nonsense-header h1 {
|
|
font-size: 1.4rem;
|
|
}
|
|
|
|
.nonsense-content {
|
|
font-size: 0.98rem;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.nonsense-list {
|
|
gap: 12px;
|
|
}
|
|
|
|
.nonsense-item {
|
|
padding: 14px 16px 10px 16px;
|
|
}
|
|
}
|
|
</style> |