feat: 实现文章状态管理及分类标签展示功能

新增文章状态管理功能,支持草稿、已发表和已删除状态的显示与切换
重构分类和标签展示模块,添加点击跳转功能
优化文章列表页面,增加状态筛选和分页功能
完善疯言疯语模块,支持编辑和删除操作
修复路由跳转和页面刷新问题
This commit is contained in:
qingfeng1121
2025-11-08 11:16:15 +08:00
parent ad893b3e5c
commit 309aeaedc1
15 changed files with 840 additions and 325 deletions

View File

@@ -1,21 +1,20 @@
<template>
<div class="nonsense-container">
<div class="nonsense-list">
<div
class="nonsense-item"
v-for="item in nonsenseList"
:key="item.id"
>
<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>
<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>
@@ -24,9 +23,11 @@
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
import {nonsenseService } from '@/services'
import { nonsenseService } from '@/services'
import { ElMessage } from 'element-plus'
import { formatRelativeTime} from '@/utils/dateUtils'
import { formatRelativeTime } from '@/utils/dateUtils'
import { useGlobalStore } from '@/store/globalStore'
const globalStore = useGlobalStore()
/**
* 吐槽数据列表
* 仅站长可见/可发
@@ -45,10 +46,10 @@ let colorChangeTimer = null
*/
const loadNonsenseList = async () => {
try {
const response = await nonsenseService.getAllNonsense()
const response = await nonsenseService.getNonsenseByStatus(1)
if (response.code === 200) {
nonsenseList.value = response.data
}else{
} else {
ElMessage.error('加载吐槽内容失败')
}
} catch (error) {
@@ -57,7 +58,62 @@ const loadNonsenseList = async () => {
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) {
@@ -82,12 +138,12 @@ const getRandomColor = () => {
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)
}
@@ -96,29 +152,29 @@ const randomChangeColors = () => {
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;
@@ -132,7 +188,7 @@ const createDigitalNoiseEffect = (selectedKeys) => {
transition: all 0.2s ease;
`;
container.appendChild(noiseEl);
// 噪点逐渐消失
setTimeout(() => {
noiseEl.style.opacity = '0';
@@ -158,10 +214,10 @@ const createSignalGlitchEffect = (selectedKeys) => {
}
charStyles.value.set(key, glitchOffset)
})
// 同时创建数字噪点效果
createDigitalNoiseEffect(selectedKeys);
// 第二步:快速恢复并闪烁
setTimeout(() => {
selectedKeys.forEach(key => {
@@ -172,7 +228,7 @@ const createSignalGlitchEffect = (selectedKeys) => {
}
charStyles.value.set(key, flashStyle)
})
// 第三步:最终设置新颜色
setTimeout(() => {
selectedKeys.forEach(key => {
@@ -185,12 +241,13 @@ const createSignalGlitchEffect = (selectedKeys) => {
charStyles.value.set(key, finalStyle)
})
}, 80)
}, 100)}
}, 100)
}
// 组件挂载时获取数据并启动定时器
onMounted(() => {
loadNonsenseList()
// 启动定时器
colorChangeTimer = setInterval(randomChangeColors, 1000)
})
@@ -216,7 +273,7 @@ onBeforeUnmount(() => {
/* 吐槽页面主容器悬浮效果 */
.nonsense-container:hover {
box-shadow: 0 4px 16px rgba(0,0,0,0.08);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
}
/* 吐槽头部样式 */
@@ -247,27 +304,34 @@ onBeforeUnmount(() => {
/* 吐槽项样式 */
.nonsense-item {
background-color: rgba(255, 255, 255, 0.85);
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);
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);
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 {
@@ -284,20 +348,20 @@ onBeforeUnmount(() => {
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;
}