feat: 实现文章状态管理及分类标签展示功能
新增文章状态管理功能,支持草稿、已发表和已删除状态的显示与切换 重构分类和标签展示模块,添加点击跳转功能 优化文章列表页面,增加状态筛选和分页功能 完善疯言疯语模块,支持编辑和删除操作 修复路由跳转和页面刷新问题
This commit is contained in:
@@ -20,7 +20,7 @@
|
|||||||
</el-icon>
|
</el-icon>
|
||||||
<span>首页</span>
|
<span>首页</span>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
<el-menu-item index="/article-list">
|
<el-menu-item index="/articlelist">
|
||||||
<el-icon>
|
<el-icon>
|
||||||
</el-icon>
|
</el-icon>
|
||||||
<span>目录</span>
|
<span>目录</span>
|
||||||
@@ -51,13 +51,13 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<a href="#" class="stat-link">
|
<a href="#" class="stat-link" @click.prevent="showCategories">
|
||||||
<span class="site-state-item-count">{{ categoryCount }}</span>
|
<span class="site-state-item-count">{{ categoryCount }}</span>
|
||||||
<span class="site-state-item-name">分类</span>
|
<span class="site-state-item-name">分类</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<a href="#" class="stat-link">
|
<a href="#" class="stat-link" @click.prevent="showAttributes">
|
||||||
<span class="site-state-item-count">{{ AttributeCount }}</span>
|
<span class="site-state-item-count">{{ AttributeCount }}</span>
|
||||||
<span class="site-state-item-name">标签</span>
|
<span class="site-state-item-name">标签</span>
|
||||||
</a>
|
</a>
|
||||||
@@ -68,6 +68,50 @@
|
|||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 分类蒙板组件 -->
|
||||||
|
<Transition name="modal">
|
||||||
|
<div v-if="showCategoryModal" class="category-modal" @click.self="closeCategoryModal">
|
||||||
|
<div class="category-modal-content">
|
||||||
|
<div class="category-modal-header">
|
||||||
|
<h3>所有分类</h3>
|
||||||
|
<button class="category-modal-close" @click="closeCategoryModal">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="category-modal-body">
|
||||||
|
<button
|
||||||
|
v-for="category in categories"
|
||||||
|
:key="category.typeid"
|
||||||
|
class="category-button"
|
||||||
|
@click="handleCategoryClick(category)"
|
||||||
|
>
|
||||||
|
{{ category.typename }} <span class="category-button-count">({{ category.count || 0 }})</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
|
||||||
|
<!-- 标签蒙板组件 -->
|
||||||
|
<Transition name="modal">
|
||||||
|
<div v-if="showAttributeModal" class="category-modal" @click.self="closeAttributeModal">
|
||||||
|
<div class="category-modal-content">
|
||||||
|
<div class="category-modal-header">
|
||||||
|
<h3>所有标签</h3>
|
||||||
|
<button class="category-modal-close" @click="closeAttributeModal">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="category-modal-body">
|
||||||
|
<button
|
||||||
|
v-for="attribute in attributes"
|
||||||
|
:key="attribute.attributeid"
|
||||||
|
class="category-button"
|
||||||
|
@click="handleAttributeClick(attribute)"
|
||||||
|
>
|
||||||
|
{{ attribute.attributename }} <span class="category-button-count">({{ attribute.count || 0 }})</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -75,6 +119,8 @@
|
|||||||
import { reactive, ref, onMounted, onUnmounted } from 'vue'
|
import { reactive, ref, onMounted, onUnmounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { articleService, categoryService, categoryAttributeService } from "@/services";
|
import { articleService, categoryService, categoryAttributeService } from "@/services";
|
||||||
|
import { useGlobalStore } from '@/store/globalStore'
|
||||||
|
const globalStore = useGlobalStore()
|
||||||
|
|
||||||
// 当前激活菜单
|
// 当前激活菜单
|
||||||
const activeIndex = ref('/:type')
|
const activeIndex = ref('/:type')
|
||||||
@@ -86,6 +132,13 @@ const state = reactive({
|
|||||||
sizeList: ['small', '', 'large'] as const,
|
sizeList: ['small', '', 'large'] as const,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 分类相关状态
|
||||||
|
const categories = ref<any[]>([])
|
||||||
|
const showCategoryModal = ref(false)
|
||||||
|
|
||||||
|
// 标签相关状态
|
||||||
|
const attributes = ref<any[]>([])
|
||||||
|
const showAttributeModal = ref(false)
|
||||||
|
|
||||||
// 处理菜单选择跳转
|
// 处理菜单选择跳转
|
||||||
const handleSelect = (key: string) => {
|
const handleSelect = (key: string) => {
|
||||||
@@ -109,40 +162,110 @@ const fetchArticleCount = async () => {
|
|||||||
try {
|
try {
|
||||||
const response = await articleService.getAllArticles();
|
const response = await articleService.getAllArticles();
|
||||||
articleCount.value = response.data?.length || 0
|
articleCount.value = response.data?.length || 0
|
||||||
// 这里应该调用API获取实际的文章数量
|
|
||||||
// 暂时设置为模拟数据
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取文章数量失败:', error)
|
console.error('获取文章数量失败:', error)
|
||||||
articleCount.value = 0
|
articleCount.value = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取分类数量
|
// 获取分类数据
|
||||||
const fetchCategoryCount = async () => {
|
const fetchCategories = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await categoryService.getAllCategories();
|
const response = await categoryService.getAllCategories();
|
||||||
categoryCount.value = response.data?.length || 0
|
// 如果API返回的数据结构不包含count属性,我们可以模拟一些数据
|
||||||
// 这里应该调用API获取实际的分类数量
|
categories.value = response.data?.map((category: any) => ({
|
||||||
// 暂时设置为模拟数据
|
...category,
|
||||||
|
count: 0
|
||||||
|
})) || [];
|
||||||
|
categories.value.forEach(async (category: any) => {
|
||||||
|
const attributeResponse = await categoryAttributeService.getAttributesByCategory(category.typeid)
|
||||||
|
if (attributeResponse.data?.length) {
|
||||||
|
category.count = attributeResponse.data?.length || 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
categoryCount.value = categories.value.length
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取分类数量失败:', error)
|
|
||||||
categoryCount.value = 0
|
console.error('获取分类失败:', error)
|
||||||
|
// 如果API调用失败,使用模拟数据
|
||||||
|
categories.value = [
|
||||||
|
|
||||||
|
];
|
||||||
|
categoryCount.value = categories.value.length
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取标签数量
|
// 获取标签数据
|
||||||
const fetchAttributeCount = async () => {
|
const fetchAttributes = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await categoryAttributeService.getAllAttributes();
|
const response = await categoryAttributeService.getAllAttributes();
|
||||||
AttributeCount.value = response.data?.length || 0
|
// 如果API返回的数据结构不包含count属性,我们可以模拟一些数据
|
||||||
// 这里应该调用API获取实际的标签数量
|
attributes.value = response.data?.map((attribute: any) => ({
|
||||||
// 暂时设置为模拟数据
|
...attribute,
|
||||||
|
count: 0
|
||||||
|
})) || [];
|
||||||
|
attributes.value.forEach(async (attribute: any) => {
|
||||||
|
const articleResponse = await articleService.getArticlesByAttributeId(attribute.attributeid)
|
||||||
|
if (articleResponse.data?.length) {
|
||||||
|
attribute.count = articleResponse.data?.length || 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
AttributeCount.value = attributes.value.length
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取标签数量失败:', error)
|
console.error('获取标签失败:', error)
|
||||||
AttributeCount.value = 0
|
// 如果API调用失败,使用模拟数据
|
||||||
|
attributes.value = [
|
||||||
|
|
||||||
|
];
|
||||||
|
AttributeCount.value = attributes.value.length
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 显示分类蒙板
|
||||||
|
const showCategories = () => {
|
||||||
|
showCategoryModal.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭分类蒙板
|
||||||
|
const closeCategoryModal = () => {
|
||||||
|
showCategoryModal.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理分类点击
|
||||||
|
const handleCategoryClick = (category: any) => {
|
||||||
|
// 这里可以根据实际需求跳转到对应分类的文章列表页
|
||||||
|
console.log('点击了分类:', category.typename)
|
||||||
|
// 示例:router.push(`/article-list?category=${category.typeid}`)
|
||||||
|
closeCategoryModal()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示标签蒙板
|
||||||
|
const showAttributes = () => {
|
||||||
|
showAttributeModal.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭标签蒙板
|
||||||
|
const closeAttributeModal = () => {
|
||||||
|
showAttributeModal.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理标签点击
|
||||||
|
const handleAttributeClick = (attribute: any) => {
|
||||||
|
// 重置全局属性状态
|
||||||
|
globalStore.removeValue('attribute')
|
||||||
|
|
||||||
|
globalStore.setValue('attribute', {
|
||||||
|
id: attribute.attributeid,
|
||||||
|
name: attribute.typename
|
||||||
|
})
|
||||||
|
console.log(attribute)
|
||||||
|
router.push({
|
||||||
|
path: '/home/aericletype',
|
||||||
|
|
||||||
|
})
|
||||||
|
closeAttributeModal()
|
||||||
|
}
|
||||||
|
|
||||||
// 控制底部模块吸顶效果
|
// 控制底部模块吸顶效果
|
||||||
const scrollY = ref(false)
|
const scrollY = ref(false)
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
@@ -153,8 +276,8 @@ const handleScroll = () => {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
window.addEventListener('scroll', handleScroll)
|
window.addEventListener('scroll', handleScroll)
|
||||||
fetchArticleCount() // 组件挂载时获取文章数量
|
fetchArticleCount() // 组件挂载时获取文章数量
|
||||||
fetchCategoryCount() // 组件挂载时获取分类数量
|
fetchCategories() // 组件挂载时获取分类数据
|
||||||
fetchAttributeCount() // 组件挂载时获取标签数量
|
fetchAttributes() // 组件挂载时获取标签数据
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
@@ -190,10 +313,17 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
/* 内容区域样式 */
|
/* 内容区域样式 */
|
||||||
#cont {
|
#cont {
|
||||||
|
padding:0 0 10px 0;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background-color: rgba(255, 255, 255, 0.9);
|
background-color: rgba(255, 255, 255, 0.9);
|
||||||
/* 白色半透明背景 */
|
/* 白色半透明背景 */
|
||||||
}
|
}
|
||||||
|
#cont .cont1{
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
#cont .cont2{
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.cont1 {
|
.cont1 {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -213,18 +343,19 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 菜单样式 */
|
/* 菜单样式 */
|
||||||
.cont2 {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cont2 .el-menu-vertical-demo {
|
.cont2 .el-menu-vertical-demo {
|
||||||
display: block;
|
display: block;
|
||||||
background-color: rgba(0, 0, 0, 0);
|
background-color: rgba(0, 0, 0, 0);
|
||||||
/* 白色半透明背景 */
|
/* 白色半透明背景 */
|
||||||
}
|
}
|
||||||
|
.cont2 .el-menu-vertical-demo li {
|
||||||
|
font-size: 14px;
|
||||||
|
height: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
.cont2 .el-menu-vertical-demo .el-menu-item:nth-child(3) {
|
.cont2 .el-menu-vertical-demo .el-menu-item:nth-child(3) {
|
||||||
border-radius: 0 0 10px 10px;
|
/* border-radius: 0 0 10px 10px; */
|
||||||
|
/* margin-bottom: 10px; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.cont2 .el-menu-vertical-demo .el-menu-item:hover {
|
.cont2 .el-menu-vertical-demo .el-menu-item:hover {
|
||||||
@@ -376,4 +507,141 @@ onUnmounted(() => {
|
|||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* 分类蒙板样式 */
|
||||||
|
.category-modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-modal-content {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 500px;
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-modal-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 15px 20px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-modal-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-modal-close {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #999;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-modal-close:hover {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-modal-body {
|
||||||
|
padding: 20px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
max-height: 60vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-button {
|
||||||
|
background-color: rgba(102, 161, 216, 0.1);
|
||||||
|
border: 1px solid rgba(102, 161, 216, 0.3);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 10px 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-button:hover {
|
||||||
|
background-color: rgba(102, 161, 216, 0.3);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 2px 8px rgba(102, 161, 216, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-button-count {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #66a1d8;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 蒙板动画 */
|
||||||
|
.modal-enter-active,
|
||||||
|
.modal-leave-active {
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-enter-from,
|
||||||
|
.modal-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-enter-active .category-modal-content,
|
||||||
|
.modal-leave-active .category-modal-content {
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-enter-from .category-modal-content {
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-leave-to .category-modal-content {
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 滚动条样式 */
|
||||||
|
.category-modal-body::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-modal-body::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-modal-body::-webkit-scrollbar-thumb {
|
||||||
|
background: #ccc;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-modal-body::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #999;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
<el-menu-item index="home">
|
<el-menu-item index="home">
|
||||||
首页
|
首页
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
<el-menu-item index="article-list">
|
<el-menu-item index="articlelist">
|
||||||
目录
|
目录
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
<el-menu-item index="nonsense">
|
<el-menu-item index="nonsense">
|
||||||
@@ -210,7 +210,7 @@ const performSearch = () => {
|
|||||||
* 根据路由路径设置页面状态
|
* 根据路由路径设置页面状态
|
||||||
*/
|
*/
|
||||||
const updatePageState = () => {
|
const updatePageState = () => {
|
||||||
if (rpsliturl[1] === localhome && rpsliturl[2] == undefined) {
|
if (rpsliturl[1] == localhome && rpsliturl[2] == undefined) {
|
||||||
classhero.value = false;
|
classhero.value = false;
|
||||||
} else {
|
} else {
|
||||||
classhero.value = true;
|
classhero.value = true;
|
||||||
@@ -225,7 +225,6 @@ const setActiveIndex = (path: string) => {
|
|||||||
globalStore.setValue('localpath', {
|
globalStore.setValue('localpath', {
|
||||||
name: path
|
name: path
|
||||||
})
|
})
|
||||||
// console.log('设置激活索引:', path);
|
|
||||||
if (path === 'message') {
|
if (path === 'message') {
|
||||||
globalStore.removeValue('articleInfo')
|
globalStore.removeValue('articleInfo')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,9 @@ const isNonsenseModalVisible = ref(false)
|
|||||||
const nonsenseContent = ref('')
|
const nonsenseContent = ref('')
|
||||||
// 基础按钮配置
|
// 基础按钮配置
|
||||||
const baseButtons = [
|
const baseButtons = [
|
||||||
{ id: 'logout', label: '登出', icon: 'icon-logout' }
|
{ id: 'logout', label: '登出', icon: 'icon-logout' },
|
||||||
|
{ id: 'reload', label: '刷新', icon: 'icon-reload' }
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
// 页面特定按钮配置
|
// 页面特定按钮配置
|
||||||
@@ -54,8 +56,9 @@ const pageButtons = {
|
|||||||
// 首页按钮
|
// 首页按钮
|
||||||
home: [
|
home: [
|
||||||
{ id: 'create-article', label: '新建', icon: 'icon-add-article' },
|
{ id: 'create-article', label: '新建', icon: 'icon-add-article' },
|
||||||
{ id: 'published-articles', label: '已发表', icon: 'icon-new-tag' },
|
{ id: 'del-articles', label: '已删除', icon: 'icon-new-tag' },
|
||||||
{ id: 'unpublished-articles', label: '未发表', icon: 'icon-new-tag' },
|
{ id: 'unpublished-articles', label: '未发表', icon: 'icon-new-tag' },
|
||||||
|
{ id: 'published-articles', label: '已发表', icon: 'icon-new-tag' },
|
||||||
...baseButtons
|
...baseButtons
|
||||||
],
|
],
|
||||||
|
|
||||||
@@ -66,7 +69,7 @@ const pageButtons = {
|
|||||||
],
|
],
|
||||||
|
|
||||||
// 分类页面按钮
|
// 分类页面按钮
|
||||||
category: [
|
articlelist: [
|
||||||
{ id: 'create-category', label: '新建', icon: 'icon-create-category' },
|
{ id: 'create-category', label: '新建', icon: 'icon-create-category' },
|
||||||
...baseButtons
|
...baseButtons
|
||||||
],
|
],
|
||||||
@@ -86,6 +89,21 @@ const pageButtons = {
|
|||||||
// 默认按钮
|
// 默认按钮
|
||||||
default: baseButtons
|
default: baseButtons
|
||||||
}
|
}
|
||||||
|
// 根据status状态获取按钮配置
|
||||||
|
const getButtonsByStatus = (status) => {
|
||||||
|
globalStore.removeValue('articlestatus')
|
||||||
|
globalStore.setValue('articlestatus', {
|
||||||
|
status: status
|
||||||
|
})
|
||||||
|
//跳转文章列表页面,添加时间戳参数确保页面刷新
|
||||||
|
try {
|
||||||
|
// 添加时间戳作为查询参数,确保页面强制刷新
|
||||||
|
const timestamp = new Date().getTime();
|
||||||
|
router.push({ path: `/home/aericlestatus`, query: { t: timestamp } })
|
||||||
|
} catch (error) {
|
||||||
|
console.error('页面跳转失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 根据当前页面返回对应的按钮配置
|
// 根据当前页面返回对应的按钮配置
|
||||||
const isbuttonsave = () => {
|
const isbuttonsave = () => {
|
||||||
@@ -151,10 +169,51 @@ const handleErrorResponse = (error, defaultMessage = '操作失败') => {
|
|||||||
console.error('操作失败:', error)
|
console.error('操作失败:', error)
|
||||||
ElMessage.error(error.message || defaultMessage)
|
ElMessage.error(error.message || defaultMessage)
|
||||||
}
|
}
|
||||||
|
// articlelist新建
|
||||||
|
const createCategory = () => {
|
||||||
|
ElMessageBox.prompt('请输入分类名称:', '新建分类', {
|
||||||
|
confirmButtonText: '保存',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
inputPlaceholder: '请输入分类名称',
|
||||||
|
inputType: 'text',
|
||||||
|
showCancelButton: true,
|
||||||
|
// 输入验证
|
||||||
|
inputValidator: (value) => {
|
||||||
|
if (!value || value.trim() === '') {
|
||||||
|
return '分类名称不能为空';
|
||||||
|
}
|
||||||
|
if (value.trim().length < 2) {
|
||||||
|
return '分类名称至少需要2个字符';
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}).then(({ value }) => {
|
||||||
|
// 保存分类
|
||||||
|
saveCategory(value.trim());
|
||||||
|
}).catch(() => {
|
||||||
|
// 取消操作,静默处理
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 保存分类
|
||||||
|
const saveCategory = (typename) => {
|
||||||
|
categoryService.saveCategory({
|
||||||
|
typename: typename
|
||||||
|
}).then(response => {
|
||||||
|
if (response.code === 200) {
|
||||||
|
ElMessage.success('分类创建成功');
|
||||||
|
// 刷新页面以显示新分类
|
||||||
|
const timestamp = new Date().getTime();
|
||||||
|
router.push({ path: `/home/aericlelist`, query: { t: timestamp } });
|
||||||
|
} else {
|
||||||
|
ElMessage.error(response.message || '创建分类失败');
|
||||||
|
}
|
||||||
|
}).catch(err => handleErrorResponse(err, '创建分类失败'));
|
||||||
|
};
|
||||||
// 删除文章方法
|
// 删除文章方法
|
||||||
const deleteArticle = () => {
|
const deleteArticle = () => {
|
||||||
if (!route.query.id) {
|
const articleId = globalStore.getValue('articleInfo')?.id
|
||||||
|
if (!articleId) {
|
||||||
ElMessage.warning('缺少文章ID参数')
|
ElMessage.warning('缺少文章ID参数')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -165,7 +224,7 @@ const deleteArticle = () => {
|
|||||||
type: 'warning'
|
type: 'warning'
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
// 调用删除文章接口
|
// 调用删除文章接口
|
||||||
articleService.deleteArticle(Number(route.query.id))
|
articleService.deleteArticle(Number(articleId))
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (response.code === 200) {
|
if (response.code === 200) {
|
||||||
ElMessage.success('文章删除成功')
|
ElMessage.success('文章删除成功')
|
||||||
@@ -180,6 +239,25 @@ const deleteArticle = () => {
|
|||||||
// 取消删除,静默处理
|
// 取消删除,静默处理
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// 修改文章方法
|
||||||
|
const updateArticle = () => {
|
||||||
|
const articleId = globalStore.getValue('articleInfo')
|
||||||
|
if (!articleId) {
|
||||||
|
ElMessage.warning('缺少文章参数')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 确认修改
|
||||||
|
ElMessageBox.confirm('确定修改该文章吗?', '修改确认', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(() => {
|
||||||
|
globalStore.setValue('updatearticle', articleId)
|
||||||
|
router.push({ path: '/articlesave' })
|
||||||
|
}).catch(() => {
|
||||||
|
// 取消修改,静默处理
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 登出方法
|
// 登出方法
|
||||||
const logout = () => {
|
const logout = () => {
|
||||||
@@ -200,7 +278,11 @@ const logout = () => {
|
|||||||
})
|
})
|
||||||
.catch(err => handleErrorResponse(err, '登出失败'))
|
.catch(err => handleErrorResponse(err, '登出失败'))
|
||||||
}
|
}
|
||||||
|
// 刷新页面
|
||||||
|
const reloadPage = () => {
|
||||||
|
globalStore.clearAll()
|
||||||
|
|
||||||
|
}
|
||||||
// 处理按钮点击事件
|
// 处理按钮点击事件
|
||||||
const handleButtonClick = (button) => {
|
const handleButtonClick = (button) => {
|
||||||
console.log('点击了按钮:', button.id, button.label)
|
console.log('点击了按钮:', button.id, button.label)
|
||||||
@@ -209,11 +291,12 @@ const handleButtonClick = (button) => {
|
|||||||
switch (button.id) {
|
switch (button.id) {
|
||||||
// 新增操作
|
// 新增操作
|
||||||
case 'create-article':
|
case 'create-article':
|
||||||
// router.push({ path: '/articlesave' })
|
// 清除更新文章状态
|
||||||
|
router.push({ path: '/articlesave' })
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'create-category':
|
case 'create-category':
|
||||||
// router.push({ path: '/categorysave' })
|
createCategory()
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'create-tag':
|
case 'create-tag':
|
||||||
@@ -227,27 +310,25 @@ const handleButtonClick = (button) => {
|
|||||||
|
|
||||||
// 修改操作
|
// 修改操作
|
||||||
case 'edit-article':
|
case 'edit-article':
|
||||||
if (route.query.id) {
|
updateArticle()
|
||||||
globalStore.setValue('localpath', { name: 'article', id: Number(route.query.id) })
|
|
||||||
router.push({ path: '/articlesave', query: { id: route.query.id } })
|
|
||||||
} else {
|
|
||||||
ElMessage.warning('缺少文章ID参数')
|
|
||||||
}
|
|
||||||
break
|
break
|
||||||
|
|
||||||
// 删除操作
|
// 删除操作
|
||||||
case 'delete-article':
|
case 'delete-article':
|
||||||
deleteArticle();
|
deleteArticle();
|
||||||
break
|
break
|
||||||
break
|
|
||||||
|
|
||||||
// 查看操作
|
// 查看操作
|
||||||
case 'published-articles':
|
case 'del-articles':
|
||||||
router.push({ path: '/home', query: { status: 1 } })
|
getButtonsByStatus(2)
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'unpublished-articles':
|
case 'unpublished-articles':
|
||||||
router.push({ path: '/home', query: { status: 0 } })
|
getButtonsByStatus(0)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'published-articles':
|
||||||
|
getButtonsByStatus(1)
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'view-articles':
|
case 'view-articles':
|
||||||
@@ -259,6 +340,10 @@ const handleButtonClick = (button) => {
|
|||||||
logout();
|
logout();
|
||||||
break
|
break
|
||||||
|
|
||||||
|
case 'reload':
|
||||||
|
reloadPage()
|
||||||
|
break
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.warn('未处理的按钮类型:', button.id, button.label)
|
console.warn('未处理的按钮类型:', button.id, button.label)
|
||||||
ElMessage.info(`功能 ${button.label} 暂未实现`)
|
ElMessage.info(`功能 ${button.label} 暂未实现`)
|
||||||
|
|||||||
@@ -26,16 +26,24 @@ const routes = [
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: 'aericletype',
|
path: 'aericletype',
|
||||||
name: 'homeByType'
|
name: 'homeByType',
|
||||||
|
component: HomePage
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'aericletitle',
|
path: 'aericletitle',
|
||||||
name: 'homeByTitle'
|
name: 'homeByTitle',
|
||||||
|
component: HomePage
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'aericlestatus',
|
||||||
|
name: 'homeByStatus',
|
||||||
|
component: HomePage
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/article-list',
|
path: '/articlelist',
|
||||||
name: 'articleList',
|
name: 'articleList',
|
||||||
component: ArticleList,
|
component: ArticleList,
|
||||||
meta: {
|
meta: {
|
||||||
|
|||||||
@@ -13,7 +13,22 @@ class ArticleService {
|
|||||||
getAllArticles(params = {}) {
|
getAllArticles(params = {}) {
|
||||||
return api.get('/articles/published', { params })
|
return api.get('/articles/published', { params })
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 根据状态获取文章列表
|
||||||
|
* @param {number} status - 文章状态(0:未发表 1:已发表 2:已删除)
|
||||||
|
* @returns {Promise<import('../types').ApiResponse<import('../types').Article[]>>}
|
||||||
|
*/
|
||||||
|
getArticlesByStatus(status) {
|
||||||
|
return api.get(`/articles/status/${status}`)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取所有文章列表(包含已删除)
|
||||||
|
* @param {import('../types').PaginationParams} params - 查询参数
|
||||||
|
* @returns {Promise<import('../types').ApiResponse<import('../types').Article[]>>}
|
||||||
|
*/
|
||||||
|
getAllArticlesWithDeleted(params = {}) {
|
||||||
|
return api.get('/articles', { params })
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 根据ID获取文章详情
|
* 根据ID获取文章详情
|
||||||
* @param {number} articleid - 文章ID
|
* @param {number} articleid - 文章ID
|
||||||
|
|||||||
@@ -3,31 +3,39 @@ import apiService from './apiService'
|
|||||||
|
|
||||||
class NonsenseService {
|
class NonsenseService {
|
||||||
/**
|
/**
|
||||||
* 获取所有随机内容
|
* 获取所有疯言疯语内容
|
||||||
* @returns {Promise<import('../types').ApiResponse<import('../types').Nonsense[]>>}
|
* @returns {Promise<import('../types').ApiResponse<import('../types').Nonsense[]>>}
|
||||||
*/
|
*/
|
||||||
getAllNonsense() {
|
getAllNonsense() {
|
||||||
return apiService.get('/nonsense')
|
return apiService.get('/nonsense')
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 保存随机内容
|
* 根据状态获取疯言疯语内容
|
||||||
* @param {import('../types').Nonsense} nonsense - 随机内容对象
|
* @param {number} status - 状态值(1:已发表, 0:草稿)
|
||||||
|
* @returns {Promise<import('../types').ApiResponse<import('../types').Nonsense[]>>}
|
||||||
|
*/
|
||||||
|
getNonsenseByStatus(status){
|
||||||
|
return apiService.get(`/nonsense/status/${status}`)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 保存疯言疯语内容
|
||||||
|
* @param {import('../types').Nonsense} nonsense - 疯言疯语内容对象
|
||||||
* @returns {Promise<import('../types').ApiResponse<import('../types').Nonsense>>}
|
* @returns {Promise<import('../types').ApiResponse<import('../types').Nonsense>>}
|
||||||
*/
|
*/
|
||||||
saveNonsense(nonsense){
|
saveNonsense(nonsense){
|
||||||
return apiService.post('/nonsense', nonsense)
|
return apiService.post('/nonsense', nonsense)
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 删除随机内容
|
* 删除疯言疯语内容
|
||||||
* @param {number} id - 随机内容ID
|
* @param {number} id - 疯言疯语内容ID
|
||||||
* @returns {Promise<import('../types').ApiResponse<boolean>>}
|
* @returns {Promise<import('../types').ApiResponse<boolean>>}
|
||||||
*/
|
*/
|
||||||
deleteNonsense(id){
|
deleteNonsense(id){
|
||||||
return apiService.delete(`/nonsense/${id}`)
|
return apiService.delete(`/nonsense/${id}`)
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 更新随机内容
|
* 更新疯言疯语内容
|
||||||
* @param {import('../types').Nonsense} nonsense - 随机内容对象
|
* @param {import('../types').Nonsense} nonsense - 疯言疯语内容对象
|
||||||
* @returns {Promise<import('../types').ApiResponse<import('../types').Nonsense>>}
|
* @returns {Promise<import('../types').ApiResponse<import('../types').Nonsense>>}
|
||||||
*/
|
*/
|
||||||
updateNonsense(nonsense){
|
updateNonsense(nonsense){
|
||||||
|
|||||||
@@ -129,6 +129,90 @@ p {
|
|||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
line-height: 1.7;
|
line-height: 1.7;
|
||||||
}
|
}
|
||||||
|
/* 编辑按钮样式 */
|
||||||
|
.edit-button{
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-button:hover {
|
||||||
|
color: rgb(247, 243, 2);
|
||||||
|
background-color: rgba(231, 205, 60, 0.3);
|
||||||
|
}
|
||||||
|
/* 删除按钮样式 */
|
||||||
|
.delete-button {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-button:hover {
|
||||||
|
color: rgb(231, 76, 60);
|
||||||
|
background-color: rgba(231, 76, 60, 0.1);
|
||||||
|
}
|
||||||
|
/* 文章span 样式 */
|
||||||
|
|
||||||
|
/* 发布日期样式 */
|
||||||
|
.article-publish-date {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #95a5a6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 阅读数量样式 */
|
||||||
|
.article-views-count {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
color: #95a5a6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-views-count::before {
|
||||||
|
content: '|';
|
||||||
|
margin-right: 12px;
|
||||||
|
color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 分类标签样式 */
|
||||||
|
.article-category-badge {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2px 10px;
|
||||||
|
background-color: rgba(52, 152, 219, 0.1);
|
||||||
|
color: #3498db;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 点赞数量样式 */
|
||||||
|
.article-likes-count {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
color: #95a5a6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-likes-count::before {
|
||||||
|
content: '|';
|
||||||
|
margin-right: 12px;
|
||||||
|
color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 评论数量样式 */
|
||||||
|
.article-comments-count {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
color: #95a5a6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-comments-count::before {
|
||||||
|
content: '|';
|
||||||
|
margin-right: 12px;
|
||||||
|
color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
/* 顶部导航栏样式 */
|
/* 顶部导航栏样式 */
|
||||||
.elrow-top {
|
.elrow-top {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export interface Article {
|
|||||||
updatedAt: string
|
updatedAt: string
|
||||||
viewCount?: number
|
viewCount?: number
|
||||||
likes?: number
|
likes?: number
|
||||||
|
commentCount?: number
|
||||||
status?: number
|
status?: number
|
||||||
markdownscontent: string
|
markdownscontent: string
|
||||||
}
|
}
|
||||||
@@ -89,6 +90,7 @@ export interface CategoryDto {
|
|||||||
* 分类属性接口
|
* 分类属性接口
|
||||||
*/
|
*/
|
||||||
export interface CategoryAttribute {
|
export interface CategoryAttribute {
|
||||||
|
map(arg0: (item: any) => any): unknown
|
||||||
attributeid: number
|
attributeid: number
|
||||||
categoryid: number
|
categoryid: number
|
||||||
attributename: string
|
attributename: string
|
||||||
@@ -127,7 +129,23 @@ export interface UserDto {
|
|||||||
phone: string
|
phone: string
|
||||||
role?: number
|
role?: number
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 疯言疯语类型接口
|
||||||
|
*/
|
||||||
|
export interface Nonsense {
|
||||||
|
nonsenseid: number
|
||||||
|
content: string
|
||||||
|
status?: number
|
||||||
|
time: string
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 疯言疯语DTO接口
|
||||||
|
*/
|
||||||
|
export interface NonsenseDto {
|
||||||
|
content: string
|
||||||
|
status?: number
|
||||||
|
time?: string
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* API响应接口
|
* API响应接口
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -15,11 +15,11 @@
|
|||||||
好像真被一阵风吹散了似的,
|
好像真被一阵风吹散了似的,
|
||||||
哈哈哈哈哈,我还蛮喜欢这个外号的。
|
哈哈哈哈哈,我还蛮喜欢这个外号的。
|
||||||
<br></br>
|
<br></br>
|
||||||
后来有一天,也不知道怎么了,精神状态不是很好,感觉要控制不住要发疯了,突然这疯子就像钉子一样扎在我脑海里(哈哈哈哈!)。于是就干脆改名叫“清疯”了——清风,清疯,念起来几乎没差,
|
有段时间,不知道怎么了,精神状态不是很好,好想发疯,突然这疯就像钉子一样扎在我脑海里(哈哈哈哈!)。于是就干脆改名叫“清疯”了——清风,清疯,念起来几乎没差,
|
||||||
但内核却从一种理想的淡然,切换成了真实的、带点毛边的鲜活。“悠悠清风荡我心”,只是如今这阵清风熬成了清疯,终于在我心里刮起一场疯——疯啦!
|
但内核却从一种理想的淡然,切换成了真实的、带点毛边的鲜活。“悠悠清风荡我心”,只是如今这阵清风熬成了清疯,终于在我心里刮起一场疯——疯啦!
|
||||||
<br></br>
|
<br></br>
|
||||||
又过些日子,玩新游戏要起名,正盯着输入框发呆,“清疯”两个字在脑海里冒出来……疯癫?哎,我好像是疯,但也没完全颠嘛!那种在理智边界试探、却绝不越线的微妙感,一下对味了——干脆就叫“清疯不颠”!
|
又过些日子,玩新游戏要起名,正盯着输入框里的“清疯”发呆, 两个字在脑海里冒出来……疯癫?哎,好像是疯了,但也没完全颠嘛!那种在理智边界试探、却绝不越线的微妙感,一下对味了——干脆就叫“清疯不颠”!
|
||||||
名字一出自己先乐了,这不就是我吗,表面疯癫,内心门儿清,简直是我们这代人精神状态的绝佳注脚。
|
名字一出自己先乐了。
|
||||||
<br></br>
|
<br></br>
|
||||||
哈哈哈哈哈哈!俗话说天才在左疯子在右,在我这儿,大概是左脑负责疯,右脑负责颠,两个家伙吵吵闹闹,反而让我在这个世界里自得其乐。
|
哈哈哈哈哈哈!俗话说天才在左疯子在右,在我这儿,大概是左脑负责疯,右脑负责颠,两个家伙吵吵闹闹,反而让我在这个世界里自得其乐。
|
||||||
</h5>
|
</h5>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<div class="about-personal-intro">
|
<div class="about-personal-intro">
|
||||||
<h4>疯言疯语</h4>
|
<h4>疯言疯语</h4>
|
||||||
<h5>
|
<h5>
|
||||||
我并没有网站的开发经验这是我的第一个项目,所以我并不清楚该如何去写这个页面。就干脆当一个我发疯的地方吧(哈哈哈哈哈 所以你有看到彩色的小鹿吗?)
|
我并没有网站的开发经验这是我的第一个项目,我想设计一下独属于清疯的页面,可是我并不清楚该如何去写这个页面。就干脆当一个我发疯的地方吧(哈哈哈哈哈 所以你有看到彩色的小鹿吗?)
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -96,7 +96,7 @@
|
|||||||
|
|
||||||
<div class="about-contact-section">
|
<div class="about-contact-section">
|
||||||
<h4>联系方式</h4>
|
<h4>联系方式</h4>
|
||||||
<p>如果你有任何问题或建议,欢迎随时联系我!</p>
|
<p>如果你有任何问题或建议,欢迎随时联系我!(我文笔真的很烂QAQ)</p>
|
||||||
<div class="contact-options">
|
<div class="contact-options">
|
||||||
<el-button type="primary" plain @click="goToMessageBoard">留言板</el-button>
|
<el-button type="primary" plain @click="goToMessageBoard">留言板</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<!-- 加载状态 -->
|
<!-- 加载状态 -->
|
||||||
<div v-if="loading" class="loading-state-container">
|
<div v-if="loading" class="loading-state-container">
|
||||||
<el-skeleton :count="5" />
|
<el-skeleton :count="1" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 错误状态 -->
|
<!-- 错误状态 -->
|
||||||
@@ -14,20 +14,19 @@
|
|||||||
<el-alert :title="error" type="error" show-icon />
|
<el-alert :title="error" type="error" show-icon />
|
||||||
<el-button type="primary" @click="fetchCategories">重新加载</el-button>
|
<el-button type="primary" @click="fetchCategories">重新加载</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 分类列表 -->
|
<!-- 分类列表 -->
|
||||||
<div v-else-if="categories.length > 0" class="article-content" id="category-list">
|
<div v-else-if="categories.length > 0" class="article-content" id="category-list">
|
||||||
<p><strong></strong></p>
|
<p><strong></strong></p>
|
||||||
<div class="alert alert-primary"><strong><span class="alert-inner-text">文章分类如下,点击跳转</span></strong></div>
|
<div class="alert alert-primary"><strong><span class="alert-inner-text">文章分类如下,点击跳转</span> </strong></div>
|
||||||
<div v-for="categoryGroup in categories" :key="categoryGroup.typeid"
|
<div v-for="categoryGroup in categories" :key="categoryGroup.typeid" class="category-group-container">
|
||||||
class="category-group-container">
|
<div v-if="categoryGroup.attributes.length > 0 && categoryGroup.attributes.some(cat => cat.articles && cat.articles.length > 0)">
|
||||||
<div v-if="categoryGroup.attributes.length > 0">
|
|
||||||
<h2 id="header-id-1">{{ categoryGroup.typename }}</h2>
|
<h2 id="header-id-1">{{ categoryGroup.typename }}</h2>
|
||||||
<span class="badge badge-primary">共 {{ categoryGroup.attributes.length }} 篇</span>
|
<!-- 计算该分类组中实际有文章的属性数量 -->
|
||||||
|
<span class="badge badge-primary">共 {{ categoryGroup.attributes.reduce((total, cat) => total + (cat.articles && cat.articles.length ? cat.articles.length : 0), 0) }} 篇</span>
|
||||||
<ul class="category-item-list">
|
<ul class="category-item-list">
|
||||||
<li v-for="category in categoryGroup.attributes" :key="category.typeid">
|
<li v-for="category in categoryGroup.attributes" :key="category.attributeid" >
|
||||||
<a class="category-link" @click="handleCategoryClick(category)"><kbd>{{ category.attributename
|
<a class="category-link" @click="handleCategoryClick(category)"><kbd>{{ category.attributename
|
||||||
}}</kbd></a>
|
}}</kbd></a>
|
||||||
— —({{ category.articles.length }})
|
— —({{ category.articles.length }})
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -66,90 +65,61 @@ const error = ref('')
|
|||||||
const fetchCategories = async () => {
|
const fetchCategories = async () => {
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
error.value = '';
|
const res = await categoryService.getAllCategories();
|
||||||
|
let processedCategories: any[] = [];
|
||||||
const res = await categoryService.getAllCategories();
|
if (res.code === 200) {
|
||||||
|
processedCategories = res.data.map(item => ({
|
||||||
if (res.data && res.data.length > 0) {
|
|
||||||
// 创建处理后的分类数组
|
|
||||||
const processedCategories = res.data.map(item => ({
|
|
||||||
...item,
|
...item,
|
||||||
attributes: [] // 使用更清晰的命名
|
attributes: [] // 使用更清晰的命名
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// 并行处理所有分类
|
// 使用Promise.all等待所有异步操作完成
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
processedCategories.map(async (category) => {
|
processedCategories.map(async category => {
|
||||||
try {
|
const attributes = await categoryAttributeService.getAttributesByCategory(category.typeid);
|
||||||
if (await categoryAttributeService.checkAttributeExists(category.typeid, category.typename || '')) {
|
if (attributes.code === 200 && Array.isArray(attributes.data)) {
|
||||||
// 获取分类的所有属性
|
const processedAttributes = await Promise.all(
|
||||||
const attributesRes = await categoryAttributeService.getAttributeById(category.typeid);
|
attributes.data.map(async item => {
|
||||||
// 存储属性数据
|
const articleItem = {
|
||||||
if (attributesRes.data) {
|
...item,
|
||||||
category.attributes = Array.isArray(attributesRes.data) ? attributesRes.data : [attributesRes.data];
|
articles: []
|
||||||
}
|
};
|
||||||
}
|
const articlesRes = await articleService.getArticlesByAttributeId(item.attributeid);
|
||||||
} catch (err) {
|
if(articlesRes.code === 200 && Array.isArray(articlesRes.data)){
|
||||||
// console.error(`处理分类失败 (分类ID: ${category.typeid}):`, err);
|
articleItem.articles = articlesRes.data;
|
||||||
|
}
|
||||||
|
return articleItem;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
category.attributes = processedAttributes;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
await getCategoryAttributes(processedCategories);
|
|
||||||
console.log('获取分类列表成功:', categories.value);
|
|
||||||
} else {
|
|
||||||
categories.value = [];
|
|
||||||
}
|
}
|
||||||
|
categories.value = processedCategories;
|
||||||
|
console.log('获取分类列表成功:', categories.value);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error.value = '获取分类列表失败,请稍后重试';
|
|
||||||
console.error('获取分类列表失败:', err);
|
console.error('获取分类列表失败:', err);
|
||||||
ElMessage.error(error.value);
|
ElMessage.error('获取分类列表失败,请稍后重试');
|
||||||
} finally {
|
}finally{
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
console.log('分类列表加载完成');
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
// 处理所有分类及其属性的文章数据
|
|
||||||
const getCategoryAttributes = async (processedCategories: any[]) => {
|
|
||||||
// 遍历所有分类
|
|
||||||
for (const category of processedCategories) {
|
|
||||||
if (category && category.attributes && category.attributes.length > 0) {
|
|
||||||
console.log(`处理分类: ${category.typename || '未命名分类'}`);
|
|
||||||
// 并行获取每个属性下的文章
|
|
||||||
await Promise.all(
|
|
||||||
category.attributes.map(async (attribute) => {
|
|
||||||
try {
|
|
||||||
// 使用正确的方法名获取属性下的文章,优先使用typeid
|
|
||||||
const idToUse = attribute.typeid || attribute.categoryid;
|
|
||||||
const articlesRes = await articleService.getArticlesByAttributeId(idToUse);
|
|
||||||
// 处理文章数据
|
|
||||||
attribute.articles = articlesRes.data && Array.isArray(articlesRes.data) ?
|
|
||||||
articlesRes.data : [];
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`获取属性文章失败 (属性ID: ${attribute.typeid || attribute.categoryid}):`, err);
|
|
||||||
attribute.articles = [];
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新分类列表
|
|
||||||
categories.value = processedCategories;
|
|
||||||
console.log('所有分类属性文章数据处理完成');
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 处理分类点击事件
|
* 处理分类点击事件
|
||||||
* 注意:现在实际上使用的是属性ID而不是分类ID
|
* 注意:现在实际上使用的是属性ID而不是分类ID
|
||||||
* @param {string | number} attributeId - 属性ID
|
* @param {string | number} attributeId - 属性ID
|
||||||
*/
|
*/
|
||||||
const handleCategoryClick = (attribute: any) => {
|
const handleCategoryClick = (attribute: any) => {
|
||||||
|
globalStore.removeValue('attribute')
|
||||||
globalStore.setValue('attribute', {
|
globalStore.setValue('attribute', {
|
||||||
id: attribute.typeid || attribute.categoryid,
|
id: attribute.attributeid,
|
||||||
name: attribute.attributename || attribute.typename || '未命名属性',
|
name: attribute.typename
|
||||||
})
|
})
|
||||||
console.log(attribute)
|
console.log(attribute)
|
||||||
router.push({
|
router.push({
|
||||||
path: '/home/aericletype',
|
path: '/home/aericletype',
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,18 +19,16 @@
|
|||||||
<h1 class="article-main-title">{{ article.title }}</h1>
|
<h1 class="article-main-title">{{ article.title }}</h1>
|
||||||
|
|
||||||
<div class="article-meta-info">
|
<div class="article-meta-info">
|
||||||
<span class="meta-item">
|
<span class="article-publish-date">{{ formatRelativeTime(article.createdAt) }}</span>
|
||||||
<i class="el-icon-date"></i>
|
<span v-if="article.categoryName" class="article-category-badge">{{ article.categoryName }} </span>
|
||||||
{{ formatDate(article.createdAt) }}
|
<span v-if="article.viewCount" class="article-views-count">{{ article.viewCount }} 阅读</span>
|
||||||
</span>
|
<span v-if="article.likes > 0" class="article-likes-count">{{ article.likes }} 点赞</span>
|
||||||
<span class="meta-item">
|
<span v-if="article.commentCount > 0" class="article-comments-count">{{ article.commentCount }} 评论</span>
|
||||||
<i class="el-icon-folder"></i>
|
<div class="article-status-badge-container" v-if="globalStore.Login">
|
||||||
{{ article.categoryName || '未分类' }}
|
<span v-if="article.status === 0" class="article-status-badge badge badge-warning">草稿</span>
|
||||||
</span>
|
<span v-if="article.status === 1" class="article-status-badge badge badge-success">已发表</span>
|
||||||
<span class="meta-item">
|
<span v-if="article.status === 2" class="article-status-badge badge badge-danger">已删除</span>
|
||||||
<i class="el-icon-view"></i>
|
</div>
|
||||||
{{ article.viewCount || 0 }} 阅读
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -85,12 +83,10 @@
|
|||||||
// 导入必要的依赖
|
// 导入必要的依赖
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { articleService } from '@/services'
|
|
||||||
import { categoryAttributeService } from '@/services'
|
|
||||||
import { useGlobalStore } from '@/store/globalStore'
|
import { useGlobalStore } from '@/store/globalStore'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import type { Article } from '@/types'
|
import type { Article } from '@/types'
|
||||||
import { formatDate } from '@/utils/dateUtils'
|
import { formatRelativeTime } from '@/utils/dateUtils'
|
||||||
import messageboard from './messageboard.vue'
|
import messageboard from './messageboard.vue'
|
||||||
import markdownViewer from './markdown.vue'
|
import markdownViewer from './markdown.vue'
|
||||||
// 路由相关
|
// 路由相关
|
||||||
@@ -122,22 +118,19 @@ const fetchArticleDetail = async () => {
|
|||||||
if (response) {
|
if (response) {
|
||||||
article.value = response
|
article.value = response
|
||||||
// 获取并设置分类名称
|
// 获取并设置分类名称
|
||||||
const categoryResponse = await categoryAttributeService.getAttributeById(Number(article.value.attributeid))
|
// const categoryResponse = await categoryAttributeService.getAttributeById(Number(article.value.attributeid))
|
||||||
article.value.categoryName = categoryResponse.data.attributename || '未分类'
|
// article.value.categoryName = categoryResponse.data.attributename || '未分类'
|
||||||
|
// 获取并设置评论量
|
||||||
// 增加文章浏览量
|
// const commentResponse = await messageService.getMessagesByArticleId(articleId)
|
||||||
try {
|
// article.value.commentCount = commentResponse.data.length || 0
|
||||||
await articleService.incrementArticleViews(Number(articleId))
|
// 更新浏览量
|
||||||
// 更新前端显示的浏览量
|
// 更新前端显示的浏览量
|
||||||
if (article.value.viewCount) {
|
if (article.value.viewCount) {
|
||||||
article.value.viewCount++
|
article.value.viewCount++
|
||||||
} else {
|
} else {
|
||||||
article.value.viewCount = 1
|
article.value.viewCount = 1
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('增加文章浏览量失败:', err)
|
|
||||||
// 不阻止主流程
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error('文章不存在或已被删除')
|
throw new Error('文章不存在或已被删除')
|
||||||
}
|
}
|
||||||
@@ -181,7 +174,7 @@ onMounted(() => {
|
|||||||
.article-detail-wrapper {
|
.article-detail-wrapper {
|
||||||
max-width: 900px;
|
max-width: 900px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
background-color: rgba(255, 255, 255, 0.85);
|
background-color: rgba(255, 255, 255, 0.85);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
padding: 40px;
|
padding: 40px;
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
@@ -193,7 +186,7 @@ onMounted(() => {
|
|||||||
max-width: 900px;
|
max-width: 900px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 40px;
|
padding: 40px;
|
||||||
background-color: rgba(255, 255, 255, 0.85);
|
background-color: rgba(255, 255, 255, 0.85);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
@@ -203,7 +196,7 @@ onMounted(() => {
|
|||||||
max-width: 900px;
|
max-width: 900px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 60px 40px;
|
padding: 60px 40px;
|
||||||
background-color: rgba(255, 255, 255, 0.85);
|
background-color: rgba(255, 255, 255, 0.85);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
@@ -218,7 +211,7 @@ onMounted(() => {
|
|||||||
max-width: 900px;
|
max-width: 900px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 80px 40px;
|
padding: 80px 40px;
|
||||||
background-color: rgba(255, 255, 255, 0.85);
|
background-color: rgba(255, 255, 255, 0.85);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<span class="meta-item status-item">
|
<span class="meta-item status-item">
|
||||||
<i class="el-icon-document"></i>
|
<i class="el-icon-document"></i>
|
||||||
<el-select v-model="Articleform.status" placeholder="请选择状态" class="meta-select">
|
<el-select v-model="Articleform.status" placeholder="请选择状态" class="meta-select">
|
||||||
<el-option v-for="item in statusoptions" :key="item.value" :label="item.label" :value="item.value">
|
<el-option v-for="item in statusoptions" :key="item.value" :label="item.label" :value="item.value">
|
||||||
</el-option>
|
</el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
@@ -45,6 +45,8 @@ import 'md-editor-v3/lib/style.css';
|
|||||||
import { categoryService, categoryAttributeService, articleService } from '@/services';
|
import { categoryService, categoryAttributeService, articleService } from '@/services';
|
||||||
import type { Article } from '@/types/index.ts';
|
import type { Article } from '@/types/index.ts';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
|
import { useGlobalStore } from '@/store/globalStore'
|
||||||
|
const globalStore = useGlobalStore()
|
||||||
// 路由
|
// 路由
|
||||||
import router from '@/router/Router';
|
import router from '@/router/Router';
|
||||||
const Articleform = ref<Article>({
|
const Articleform = ref<Article>({
|
||||||
@@ -55,7 +57,8 @@ const Articleform = ref<Article>({
|
|||||||
categoryName: '',
|
categoryName: '',
|
||||||
createdAt: '',
|
createdAt: '',
|
||||||
updatedAt: '',
|
updatedAt: '',
|
||||||
markdownscontent: ''
|
markdownscontent: '',
|
||||||
|
status: 0 // 默认状态为草稿
|
||||||
})
|
})
|
||||||
|
|
||||||
// 用于级联选择器的值绑定
|
// 用于级联选择器的值绑定
|
||||||
@@ -63,16 +66,29 @@ const selectedValues = ref([]);
|
|||||||
const categorieoptions = ref([]);
|
const categorieoptions = ref([]);
|
||||||
const statusoptions = ref([
|
const statusoptions = ref([
|
||||||
{
|
{
|
||||||
label: '未发布',
|
label: '草稿',
|
||||||
value: '0'
|
value: '0'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '发布',
|
label: '发布',
|
||||||
value: '1'
|
value: '1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '删除',
|
||||||
|
value: '2'
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
const categories = ref([]);
|
const categories = ref([]);
|
||||||
|
// 编辑文章
|
||||||
|
const editArticle = globalStore.getValue('updatearticle')
|
||||||
|
|
||||||
|
if (editArticle) {
|
||||||
|
Articleform.value = {
|
||||||
|
...editArticle,
|
||||||
|
// 确保status是字符串格式,与statusoptions的value格式匹配
|
||||||
|
status: editArticle.status !== undefined ? String(editArticle.status) : '0'
|
||||||
|
}
|
||||||
|
}
|
||||||
// 初始化加载分类和属性,构建级联选择器的options
|
// 初始化加载分类和属性,构建级联选择器的options
|
||||||
const loadCategories = async () => {
|
const loadCategories = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -107,7 +123,22 @@ const loadCategories = async () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
categorieoptions.value = optionsData;
|
categorieoptions.value = optionsData;
|
||||||
console.log(optionsData);
|
|
||||||
|
// 如果是编辑模式且有attributeid,设置级联选择器的默认值
|
||||||
|
if (Articleform.value.articleid !== 0 && Articleform.value.attributeid !== 0) {
|
||||||
|
// 查找属性所属的分类
|
||||||
|
for (const category of optionsData) {
|
||||||
|
const foundAttribute = category.children.find(attr => attr.value === Articleform.value.attributeid.toString());
|
||||||
|
if (foundAttribute) {
|
||||||
|
// 设置级联选择器的值:[分类ID, 属性ID]
|
||||||
|
selectedValues.value = [category.value, foundAttribute.value];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('分类选项:', optionsData);
|
||||||
|
console.log('选中的值:', selectedValues.value);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载分类失败:', error);
|
console.error('加载分类失败:', error);
|
||||||
@@ -138,6 +169,7 @@ const handleSave = (markdown) => {
|
|||||||
|
|
||||||
// 构建请求数据
|
// 构建请求数据
|
||||||
const articleData = {
|
const articleData = {
|
||||||
|
articleid: Articleform.value.articleid,
|
||||||
title: Articleform.value.title,
|
title: Articleform.value.title,
|
||||||
content: Articleform.value.content,
|
content: Articleform.value.content,
|
||||||
attributeid: Number(Articleform.value.attributeid),
|
attributeid: Number(Articleform.value.attributeid),
|
||||||
@@ -150,28 +182,37 @@ const handleSave = (markdown) => {
|
|||||||
console.log('发送文章数据:', articleData);
|
console.log('发送文章数据:', articleData);
|
||||||
console.log('当前认证token是否存在:', !!localStorage.getItem('token'));
|
console.log('当前认证token是否存在:', !!localStorage.getItem('token'));
|
||||||
|
|
||||||
// 保存文章
|
// 根据articleid决定调用创建还是更新接口
|
||||||
articleService.createArticle(articleData)
|
const savePromise = Articleform.value.articleid === 0
|
||||||
|
? articleService.createArticle(articleData)
|
||||||
|
: articleService.updateArticle(Articleform.value.articleid, articleData);
|
||||||
|
savePromise
|
||||||
.then(res => {
|
.then(res => {
|
||||||
console.log('API响应:', res);
|
console.log('API响应:', res);
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
ElMessage.success('文章保存成功')
|
ElMessage.success(Articleform.value.articleid === 0 ? '文章创建成功' : '文章更新成功');
|
||||||
// 重置表单
|
// 清除全局存储中的article
|
||||||
Articleform.value = {
|
globalStore.removeValue('updatearticle');
|
||||||
articleid: 0,
|
// 重置表单或保留编辑状态
|
||||||
title: '',
|
if (Articleform.value.articleid === 0) {
|
||||||
content: '',
|
// 创建新文章后重置表单
|
||||||
attributeid: 0,
|
Articleform.value = {
|
||||||
categoryName: '',
|
articleid: 0,
|
||||||
createdAt: '',
|
title: '',
|
||||||
updatedAt: '',
|
content: '',
|
||||||
markdownscontent: ''
|
attributeid: 0,
|
||||||
};
|
categoryName: '',
|
||||||
selectedValues.value = [];
|
createdAt: '',
|
||||||
|
updatedAt: '',
|
||||||
|
markdownscontent: ''
|
||||||
|
};
|
||||||
|
selectedValues.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
// 返回列表页
|
// 返回列表页
|
||||||
router.push('/home');
|
router.push('/home');
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error(res.message || '文章保存失败')
|
ElMessage.error(res.message || (Articleform.value.articleid === 0 ? '文章创建失败' : '文章更新失败'));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
@@ -181,17 +222,19 @@ const handleSave = (markdown) => {
|
|||||||
console.error('错误状态码:', err.response.status);
|
console.error('错误状态码:', err.response.status);
|
||||||
console.error('错误响应数据:', err.response.data);
|
console.error('错误响应数据:', err.response.data);
|
||||||
|
|
||||||
|
const operationType = Articleform.value.articleid === 0 ? '创建' : '更新';
|
||||||
|
|
||||||
if (err.response.status === 401) {
|
if (err.response.status === 401) {
|
||||||
ElMessage.error('未授权访问,请先登录');
|
ElMessage.error('未授权访问,请先登录');
|
||||||
} else if (err.response.status === 403) {
|
} else if (err.response.status === 403) {
|
||||||
ElMessage.error('没有权限创建文章,请检查账号权限');
|
ElMessage.error(`没有权限${operationType}文章,请检查账号权限`);
|
||||||
} else if (err.response.status === 400) {
|
} else if (err.response.status === 400) {
|
||||||
ElMessage.error('数据验证失败: ' + (err.response.data?.message || '请检查输入'));
|
ElMessage.error('数据验证失败: ' + (err.response.data?.message || '请检查输入'));
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error('请求被拒绝,错误代码: ' + err.response.status);
|
ElMessage.error(`请求被拒绝,错误代码: ${err.response.status}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error(err.message || '文章保存失败')
|
ElMessage.error(err.message || (Articleform.value.articleid === 0 ? '文章创建失败' : '文章更新失败'));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,10 +20,15 @@
|
|||||||
<p class="article-content-preview">{{ formatContentPreview(article.content, 150) }}</p>
|
<p class="article-content-preview">{{ formatContentPreview(article.content, 150) }}</p>
|
||||||
<div class="article-meta-info">
|
<div class="article-meta-info">
|
||||||
<span class="article-publish-date">{{ formatRelativeTime(article.createdAt || article.createTime) }}</span>
|
<span class="article-publish-date">{{ formatRelativeTime(article.createdAt || article.createTime) }}</span>
|
||||||
|
<span v-if="article.categoryName" class="article-category-badge">{{ article.categoryName }} </span>
|
||||||
<span v-if="article.viewCount" class="article-views-count">{{ article.viewCount }} 阅读</span>
|
<span v-if="article.viewCount" class="article-views-count">{{ article.viewCount }} 阅读</span>
|
||||||
<span v-if="article.categoryName" class="article-category-badge"> {{ article.categoryName }} </span>
|
|
||||||
<span v-if="article.likes > 0" class="article-likes-count">{{ article.likes }} 点赞</span>
|
<span v-if="article.likes > 0" class="article-likes-count">{{ article.likes }} 点赞</span>
|
||||||
<span v-if="article.commentCount > 0" class="article-comments-count">{{ article.commentCount }} 评论</span>
|
<span v-if="article.commentCount > 0" class="article-comments-count">{{ article.commentCount }} 评论</span>
|
||||||
|
<div class="article-status-badge-container" v-if="globalStore.Login">
|
||||||
|
<span v-if="article.status === 0" class="article-status-badge badge badge-warning">草稿</span>
|
||||||
|
<span v-if="article.status === 1" class="article-status-badge badge badge-success">已发表</span>
|
||||||
|
<span v-if="article.status === 2" class="article-status-badge badge badge-danger">已删除</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</transition-group>
|
</transition-group>
|
||||||
@@ -36,7 +41,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted, watch } from 'vue'
|
||||||
import { formatDate, formatRelativeTime } from '@/utils/dateUtils'
|
import { formatDate, formatRelativeTime } from '@/utils/dateUtils'
|
||||||
import { formatContentPreview } from '@/utils/stringUtils'
|
import { formatContentPreview } from '@/utils/stringUtils'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
@@ -64,20 +69,35 @@ const fetchArticles = async () => {
|
|||||||
|
|
||||||
// 检查URL参数,确定获取文章的方式
|
// 检查URL参数,确定获取文章的方式
|
||||||
const pathSegment = route.path.split('/')[2];
|
const pathSegment = route.path.split('/')[2];
|
||||||
|
console.log(pathSegment)
|
||||||
// 根据不同路径获取不同文章
|
// 根据不同路径获取不同文章
|
||||||
if (pathSegment === 'aericletype') {
|
switch (pathSegment) {
|
||||||
// 按属性类型获取文章
|
case 'aericletype':
|
||||||
const attributeData = globalStore.getValue('attribute')
|
// 按属性类型获取文章
|
||||||
response = await articleService.getArticlesByAttributeId(attributeData.id)
|
const attributeData = globalStore.getValue('attribute')
|
||||||
} else if (pathSegment === 'aericletitle') {
|
response = await articleService.getArticlesByAttributeId(attributeData.id)
|
||||||
// 按标题搜索文章
|
break
|
||||||
const titleData = globalStore.getValue('articleserarch')
|
case 'aericletitle':
|
||||||
response = await articleService.getArticlesByTitle(titleData.name)
|
// 按标题搜索文章
|
||||||
} else {
|
const titleData = globalStore.getValue('articleserarch')
|
||||||
// 获取所有文章
|
response = await articleService.getArticlesByTitle(titleData.name)
|
||||||
console.log('获取所有文章列表')
|
break
|
||||||
response = await articleService.getAllArticles()
|
case 'aericlestatus':
|
||||||
|
// 按状态获取文章
|
||||||
|
const statusData = globalStore.getValue('articlestatus')
|
||||||
|
response = await articleService.getArticlesByStatus(statusData.status)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
// 默认情况下,根据用户权限决定获取方式
|
||||||
|
if (globalStore.Login) {
|
||||||
|
// 获取所有文章(包含已删除)
|
||||||
|
console.log('管理员获取所有文章列表(包含已删除)')
|
||||||
|
response = await articleService.getAllArticlesWithDeleted()
|
||||||
|
} else {
|
||||||
|
// 获取所有文章
|
||||||
|
console.log('获取所有文章列表')
|
||||||
|
response = await articleService.getAllArticles()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 为每个文章获取留言数量和分类名称
|
// 为每个文章获取留言数量和分类名称
|
||||||
@@ -126,7 +146,10 @@ const fetchArticles = async () => {
|
|||||||
* @param {Object} article - 文章对象
|
* @param {Object} article - 文章对象
|
||||||
*/
|
*/
|
||||||
const handleArticleClick = (article) => {
|
const handleArticleClick = (article) => {
|
||||||
|
// 增加文章浏览量
|
||||||
|
articleService.incrementArticleViews(article.articleId)
|
||||||
|
// 清除之前的文章信息
|
||||||
|
globalStore.removeValue('articleInfo')
|
||||||
// 存储文章信息到全局状态
|
// 存储文章信息到全局状态
|
||||||
globalStore.setValue('articleInfo', article)
|
globalStore.setValue('articleInfo', article)
|
||||||
|
|
||||||
@@ -135,12 +158,24 @@ const handleArticleClick = (article) => {
|
|||||||
path: '/article',
|
path: '/article',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
//刷新时挂载获取数据
|
||||||
|
|
||||||
// 组件挂载时获取数据
|
// 组件挂载时获取数据
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchArticles()
|
fetchArticles()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 监听路由变化,确保刷新时也能重新获取数据
|
||||||
|
watch(
|
||||||
|
// 监听路由路径和查询参数变化
|
||||||
|
() => [route.path, route.query],
|
||||||
|
// 路由变化时触发获取文章列表
|
||||||
|
() => {
|
||||||
|
fetchArticles()
|
||||||
|
console.log('路由变化,重新获取文章列表')
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -261,69 +296,6 @@ onMounted(() => {
|
|||||||
border-top: 1px solid rgba(0, 0, 0, 0.05);
|
border-top: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 发布日期样式 */
|
|
||||||
.article-publish-date {
|
|
||||||
font-weight: 500;
|
|
||||||
color: #95a5a6;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 阅读数量样式 */
|
|
||||||
.article-views-count {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
color: #95a5a6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-views-count::before {
|
|
||||||
content: '|';
|
|
||||||
margin-right: 12px;
|
|
||||||
color: #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 分类标签样式 */
|
|
||||||
.article-category-badge {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 2px 10px;
|
|
||||||
background-color: rgba(52, 152, 219, 0.1);
|
|
||||||
color: #3498db;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-category-badge::before {
|
|
||||||
content: '|';
|
|
||||||
margin-right: 12px;
|
|
||||||
color: #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 点赞数量样式 */
|
|
||||||
.article-likes-count {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
color: #95a5a6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-likes-count::before {
|
|
||||||
content: '|';
|
|
||||||
margin-right: 12px;
|
|
||||||
color: #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 评论数量样式 */
|
|
||||||
.article-comments-count {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
color: #95a5a6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-comments-count::before {
|
|
||||||
content: '|';
|
|
||||||
margin-right: 12px;
|
|
||||||
color: #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 空状态容器样式 */
|
/* 空状态容器样式 */
|
||||||
.empty-state-container {
|
.empty-state-container {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@@ -701,19 +701,7 @@ const handleDelete = async (msg) => {
|
|||||||
background-color: rgba(64, 158, 255, 0.1);
|
background-color: rgba(64, 158, 255, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除按钮
|
|
||||||
.delete-button {
|
|
||||||
cursor: pointer;
|
|
||||||
transition: color 0.3s ease;
|
|
||||||
padding: 4px 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete-button:hover {
|
|
||||||
color: #e74c3c;
|
|
||||||
background-color: rgba(231, 76, 60, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 回复列表容器 */
|
/* 回复列表容器 */
|
||||||
.reply-list-container {
|
.reply-list-container {
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="nonsense-container">
|
<div class="nonsense-container">
|
||||||
<div class="nonsense-list">
|
<div class="nonsense-list">
|
||||||
<div
|
<div class="nonsense-item" v-for="item in nonsenseList" :key="item.id">
|
||||||
class="nonsense-item"
|
|
||||||
v-for="item in nonsenseList"
|
|
||||||
:key="item.id"
|
|
||||||
>
|
|
||||||
<div class="nonsense-meta-info">
|
<div class="nonsense-meta-info">
|
||||||
<span class="nonsense-time">{{ formatRelativeTime(item.time) }}</span>
|
<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>
|
||||||
<div class="nonsense-content">
|
<div class="nonsense-content">
|
||||||
<span
|
<span v-for="(char, index) in item.content.split('')" :key="index" :ref="el => setCharRef(el, item.id, index)"
|
||||||
v-for="(char, index) in item.content.split('')"
|
:style="getCharStyle(item.id, index)">{{ char }}</span>
|
||||||
:key="index"
|
|
||||||
:ref="el => setCharRef(el, item.id, index)"
|
|
||||||
:style="getCharStyle(item.id, index)"
|
|
||||||
>{{ char }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -24,9 +23,11 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||||
import {nonsenseService } from '@/services'
|
import { nonsenseService } from '@/services'
|
||||||
import { ElMessage } from 'element-plus'
|
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 () => {
|
const loadNonsenseList = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await nonsenseService.getAllNonsense()
|
const response = await nonsenseService.getNonsenseByStatus(1)
|
||||||
if (response.code === 200) {
|
if (response.code === 200) {
|
||||||
nonsenseList.value = response.data
|
nonsenseList.value = response.data
|
||||||
}else{
|
} else {
|
||||||
ElMessage.error('加载吐槽内容失败')
|
ElMessage.error('加载吐槽内容失败')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -57,7 +58,62 @@ const loadNonsenseList = async () => {
|
|||||||
console.log('加载吐槽内容完成')
|
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) => {
|
const setCharRef = (el, itemId, index) => {
|
||||||
if (el) {
|
if (el) {
|
||||||
@@ -82,12 +138,12 @@ const getRandomColor = () => {
|
|||||||
const randomChangeColors = () => {
|
const randomChangeColors = () => {
|
||||||
const keys = Array.from(charRefs.value.keys())
|
const keys = Array.from(charRefs.value.keys())
|
||||||
if (keys.length === 0) return
|
if (keys.length === 0) return
|
||||||
|
|
||||||
// 随机选择20-30%的字符改变颜色
|
// 随机选择20-30%的字符改变颜色
|
||||||
const countToChange = Math.floor(keys.length * (Math.random() * 0.1 + 0.2))
|
const countToChange = Math.floor(keys.length * (Math.random() * 0.1 + 0.2))
|
||||||
const shuffledKeys = [...keys].sort(() => 0.5 - Math.random())
|
const shuffledKeys = [...keys].sort(() => 0.5 - Math.random())
|
||||||
const selectedKeys = shuffledKeys.slice(0, countToChange)
|
const selectedKeys = shuffledKeys.slice(0, countToChange)
|
||||||
|
|
||||||
// 创建信号故障效果
|
// 创建信号故障效果
|
||||||
createSignalGlitchEffect(selectedKeys)
|
createSignalGlitchEffect(selectedKeys)
|
||||||
}
|
}
|
||||||
@@ -96,29 +152,29 @@ const randomChangeColors = () => {
|
|||||||
const createDigitalNoiseEffect = (selectedKeys) => {
|
const createDigitalNoiseEffect = (selectedKeys) => {
|
||||||
// 噪点字符集
|
// 噪点字符集
|
||||||
const noiseChars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+-=[]{}|;:,.<>?';
|
const noiseChars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+-=[]{}|;:,.<>?';
|
||||||
|
|
||||||
selectedKeys.forEach(key => {
|
selectedKeys.forEach(key => {
|
||||||
const charElement = charRefs.value.get(key);
|
const charElement = charRefs.value.get(key);
|
||||||
if (charElement) {
|
if (charElement) {
|
||||||
const rect = charElement.getBoundingClientRect();
|
const rect = charElement.getBoundingClientRect();
|
||||||
const container = charElement.closest('.nonsense-item');
|
const container = charElement.closest('.nonsense-item');
|
||||||
const containerRect = container.getBoundingClientRect();
|
const containerRect = container.getBoundingClientRect();
|
||||||
|
|
||||||
// 生成3-5个噪点
|
// 生成3-5个噪点
|
||||||
const noiseCount = Math.floor(Math.random() * 3) + 3;
|
const noiseCount = Math.floor(Math.random() * 3) + 3;
|
||||||
|
|
||||||
for (let i = 0; i < noiseCount; i++) {
|
for (let i = 0; i < noiseCount; i++) {
|
||||||
const noiseEl = document.createElement('span');
|
const noiseEl = document.createElement('span');
|
||||||
noiseEl.textContent = noiseChars.charAt(Math.floor(Math.random() * noiseChars.length));
|
noiseEl.textContent = noiseChars.charAt(Math.floor(Math.random() * noiseChars.length));
|
||||||
|
|
||||||
// 随机位置相对于原字符
|
// 随机位置相对于原字符
|
||||||
const x = Math.random() * 30 - 20; // -20到20px
|
const x = Math.random() * 30 - 20; // -20到20px
|
||||||
const y = Math.random() * 20 - 15; // -15到15px
|
const y = Math.random() * 20 - 15; // -15到15px
|
||||||
|
|
||||||
// 随机大小和透明度
|
// 随机大小和透明度
|
||||||
const size = Math.random() * 8 + 8; // 8-16px
|
const size = Math.random() * 8 + 8; // 8-16px
|
||||||
const opacity = Math.random() * 0.6 + 0.2; // 0.2-0.8
|
const opacity = Math.random() * 0.6 + 0.2; // 0.2-0.8
|
||||||
|
|
||||||
noiseEl.style.cssText = `
|
noiseEl.style.cssText = `
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: ${rect.left - containerRect.left + x}px;
|
left: ${rect.left - containerRect.left + x}px;
|
||||||
@@ -132,7 +188,7 @@ const createDigitalNoiseEffect = (selectedKeys) => {
|
|||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
`;
|
`;
|
||||||
container.appendChild(noiseEl);
|
container.appendChild(noiseEl);
|
||||||
|
|
||||||
// 噪点逐渐消失
|
// 噪点逐渐消失
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
noiseEl.style.opacity = '0';
|
noiseEl.style.opacity = '0';
|
||||||
@@ -158,10 +214,10 @@ const createSignalGlitchEffect = (selectedKeys) => {
|
|||||||
}
|
}
|
||||||
charStyles.value.set(key, glitchOffset)
|
charStyles.value.set(key, glitchOffset)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 同时创建数字噪点效果
|
// 同时创建数字噪点效果
|
||||||
createDigitalNoiseEffect(selectedKeys);
|
createDigitalNoiseEffect(selectedKeys);
|
||||||
|
|
||||||
// 第二步:快速恢复并闪烁
|
// 第二步:快速恢复并闪烁
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
selectedKeys.forEach(key => {
|
selectedKeys.forEach(key => {
|
||||||
@@ -172,7 +228,7 @@ const createSignalGlitchEffect = (selectedKeys) => {
|
|||||||
}
|
}
|
||||||
charStyles.value.set(key, flashStyle)
|
charStyles.value.set(key, flashStyle)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 第三步:最终设置新颜色
|
// 第三步:最终设置新颜色
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
selectedKeys.forEach(key => {
|
selectedKeys.forEach(key => {
|
||||||
@@ -185,12 +241,13 @@ const createSignalGlitchEffect = (selectedKeys) => {
|
|||||||
charStyles.value.set(key, finalStyle)
|
charStyles.value.set(key, finalStyle)
|
||||||
})
|
})
|
||||||
}, 80)
|
}, 80)
|
||||||
}, 100)}
|
}, 100)
|
||||||
|
}
|
||||||
|
|
||||||
// 组件挂载时获取数据并启动定时器
|
// 组件挂载时获取数据并启动定时器
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadNonsenseList()
|
loadNonsenseList()
|
||||||
|
|
||||||
// 启动定时器
|
// 启动定时器
|
||||||
colorChangeTimer = setInterval(randomChangeColors, 1000)
|
colorChangeTimer = setInterval(randomChangeColors, 1000)
|
||||||
})
|
})
|
||||||
@@ -216,7 +273,7 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
/* 吐槽页面主容器悬浮效果 */
|
/* 吐槽页面主容器悬浮效果 */
|
||||||
.nonsense-container:hover {
|
.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 {
|
.nonsense-item {
|
||||||
background-color: rgba(255, 255, 255, 0.85);
|
background-color: rgba(255, 255, 255, 0.85);
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
padding: 18px 20px 14px 20px;
|
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;
|
position: relative;
|
||||||
transition: box-shadow 0.2s, transform 0.2s ease;
|
transition: box-shadow 0.2s, transform 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 吐槽项悬浮效果 */
|
/* 吐槽项悬浮效果 */
|
||||||
.nonsense-item:hover {
|
.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);
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 吐槽元信息样式 */
|
/* 吐槽元信息样式 */
|
||||||
.nonsense-meta-info {
|
.nonsense-meta-info {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
.nonsense-meta-info span {
|
||||||
|
padding: 4px 8px;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
/* 吐槽内容样式 */
|
/* 吐槽内容样式 */
|
||||||
.nonsense-content {
|
.nonsense-content {
|
||||||
@@ -284,20 +348,20 @@ onBeforeUnmount(() => {
|
|||||||
padding: 14px 4px 10px 4px;
|
padding: 14px 4px 10px 4px;
|
||||||
margin: 0 8px;
|
margin: 0 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nonsense-header h1 {
|
.nonsense-header h1 {
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nonsense-content {
|
.nonsense-content {
|
||||||
font-size: 0.98rem;
|
font-size: 0.98rem;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nonsense-list {
|
.nonsense-list {
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nonsense-item {
|
.nonsense-item {
|
||||||
padding: 14px 16px 10px 16px;
|
padding: 14px 16px 10px 16px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user