refactor(store): 移除未使用的Login导入并优化全局状态管理

refactor(router): 重构路由守卫逻辑,简化登录状态检查

refactor(components): 重构LeftModule组件,使用分类树数据并移除冗余代码

refactor(layouts): 优化MainLayout组件,使用分类树数据并简化模态框逻辑

refactor(views): 重构aericlelist视图,使用分类树数据并优化文章计数逻辑
This commit is contained in:
qingfeng1121
2025-12-25 13:25:26 +08:00
parent 47df45277e
commit 3828a51aeb
7 changed files with 100 additions and 195 deletions

View File

@@ -77,7 +77,7 @@
<script lang="ts" setup> <script lang="ts" setup>
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 { categoryService } from "@/services";
// 当前激活菜单 // 当前激活菜单
const activeIndex = ref('/:type') const activeIndex = ref('/:type')
@@ -92,6 +92,9 @@ const state = reactive({
// 分类相关状态 // 分类相关状态
// defineEmits // defineEmits
const emit = defineEmits(['update-data', 'CategoryModal', 'AttributeModal']) const emit = defineEmits(['update-data', 'CategoryModal', 'AttributeModal'])
const categoryCount = ref(0)
const AttributeCount = ref(0)
const articleCount = ref(0)
// 标签相关状态 // 标签相关状态
// 处理菜单选择跳转 // 处理菜单选择跳转
@@ -103,87 +106,36 @@ const handleSelect = (key: string) => {
router.beforeEach((to) => { router.beforeEach((to) => {
activeIndex.value = to.path activeIndex.value = to.path
}) })
// 分类树遍历函数
// 文章数量状态 const traverseCategoryTree = (tree) => {
const articleCount = ref(0) categoryCount.value = tree.length
// 分类数量状态 tree.forEach((category) => {
const categoryCount = ref(0) category.children.forEach((child) => {
// 标签数量状态 articleCount.value += child.articlecount || 0
const AttributeCount = ref(0) })
AttributeCount.value += category.children.length || 0
// 获取文章数量 })
const fetchArticleCount = async () => {
try {
const response = await articleService.getAllArticles();
articleCount.value = response.data?.length || 0
} catch (error) {
console.error('获取文章数量失败:', error)
articleCount.value = 0
} }
}
// 获取分类数据 // 获取分类数据
const fetchCategories = async () => { const fetchCategoriestree = async () => {
// 分类数据状态 // 分类数据状态
const categories = ref<any[]>([])
try { try {
const response = await categoryService.getAllCategories(); const response = await categoryService.getCategoryTree()
// 如果API返回的数据结构不包含count属性我们可以模拟一些数据 // 如果API返回的数据结构不包含count属性我们可以模拟一些数据
categories.value = response.data?.map((category: any) => ({ traverseCategoryTree(response.data || [])
...category, return response.data || []
count: 0
})) || [];
categories.value.forEach(async (category: any) => {
const attributeResponse = await categoryAttributeService.getAttributesByCategory(category.categoryid)
if (attributeResponse.data?.length) {
category.count = attributeResponse.data?.length || 0
}
})
categoryCount.value = categories.value.length
} catch (error) { } catch (error) {
console.error('获取分类失败:', error) console.error('获取分类失败:', error)
// 如果API调用失败使用模拟数据 // 如果API调用失败使用模拟数据
categories.value = [ return []
];
categoryCount.value = categories.value.length
} }
return categories.value
} }
// 获取标签数据
const fetchAttributes = async () => {
// 标签数据状态
const attributes = ref<any[]>([])
try {
const response = await categoryAttributeService.getAllAttributes();
// 如果API返回的数据结构不包含count属性我们可以模拟一些数据
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) {
console.error('获取标签失败:', error)
// 如果API调用失败使用模拟数据
attributes.value = [
];
AttributeCount.value = attributes.value.length
}
return attributes.value
}
// 向父组件传递标签数据 // 向父组件传递标签数据
const sendData = () => { const sendData = () => {
const data = { fetchAttributes: fetchAttributes(), fetchCategories: fetchCategories() } const data = { fetchCategoriestree: fetchCategoriestree() }
emit('update-data', data) emit('update-data', data)
} }
@@ -206,7 +158,6 @@ const handleScroll = () => {
// 生命周期管理事件监听,防止内存泄漏 // 生命周期管理事件监听,防止内存泄漏
onMounted(() => { onMounted(() => {
window.addEventListener('scroll', handleScroll) window.addEventListener('scroll', handleScroll)
fetchArticleCount() // 组件挂载时获取文章数量
sendData() // 组件挂载时获取标签数据和分类数据 sendData() // 组件挂载时获取标签数据和分类数据
}) })

View File

@@ -83,9 +83,9 @@
<button class="category-modal-close" @click="closeCategoryModal">×</button> <button class="category-modal-close" @click="closeCategoryModal">×</button>
</div> </div>
<div class="category-modal-body"> <div class="category-modal-body">
<button v-for="category in categories" :key="category.typeid" class="category-button" <button v-for="category in Categoriestree" :key="category.typeid" class="category-button"
@click="handleCategoryClick(category)"> @click="handleCategoryClick(category)">
{{ category.typename }} <span class="category-button-count">({{ category.count || 0 }})</span> {{ category.name }} <span class="category-button-count">({{ category.children?.length || 0 }})</span>
</button> </button>
</div> </div>
</div> </div>
@@ -101,9 +101,10 @@
<button class="category-modal-close" @click="closeAttributeModal">×</button> <button class="category-modal-close" @click="closeAttributeModal">×</button>
</div> </div>
<div class="category-modal-body"> <div class="category-modal-body">
<button v-for="attribute in attributes" :key="attribute.attributeid" class="category-button" <!-- 扁平化处理 -->
<button v-for="attribute in getAllAttributes()" :key="attribute.attributeid" class="category-button"
@click="handleAttributeClick(attribute)"> @click="handleAttributeClick(attribute)">
{{ attribute.attributename }} <span class="category-button-count">({{ attribute.count || 0 }})</span> {{ attribute.attributename }} <span class="category-button-count">({{ attribute.articlecount || 0 }})</span>
</button> </button>
</div> </div>
</div> </div>
@@ -122,7 +123,7 @@ import Footer from '@/views/Footer.vue';
// ========== 组件初始化 ========== // ========== 组件初始化 ==========
// 路由相关 // 路由相关状态
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();
@@ -173,22 +174,13 @@ let heroIndex = 0;
let heroTimer: number | undefined; let heroTimer: number | undefined;
// 蒙版相关状态 // 蒙版相关状态
const categories = ref<any[]>([]) const Categoriestree = ref<any[]>([])
const showCategoryModal = ref(false) const showCategoryModal = ref(false)
const attributes = ref<any[]>([])
const showAttributeModal = ref(false) const showAttributeModal = ref(false)
// 显示分类蒙板
const openCategoryModal = () => {
showCategoryModal.value = true;
}
// 关闭分类蒙板 // 关闭分类蒙板
const closeCategoryModal = () => { const closeCategoryModal = () => {
showCategoryModal.value = false; showCategoryModal.value = false;
} }
// 显示标签蒙板
const openAttributeModal = () => {
showAttributeModal.value = true;
}
// 关闭标签蒙板 // 关闭标签蒙板
const closeAttributeModal = () => { const closeAttributeModal = () => {
showAttributeModal.value = false; showAttributeModal.value = false;
@@ -196,40 +188,22 @@ const closeAttributeModal = () => {
// 左侧状态栏传值 // 左侧状态栏传值
const updateData = (data: any) => { const updateData = (data: any) => {
// 处理异步数据 // 处理异步数据
if (data.fetchCategories && typeof data.fetchCategories.then === 'function') { // console.log(data.fetchCategoriestree)
data.fetchCategories.then(result => { if (data.fetchCategoriestree && typeof data.fetchCategoriestree.then === 'function') {
categories.value = result || [] data.fetchCategoriestree.then(result => {
Categoriestree.value = result || []
}) })
} else { } else {
categories.value = data.fetchCategories || [] Categoriestree.value = data.fetchCategoriestree || []
}
if (data.fetchAttributes && typeof data.fetchAttributes.then === 'function') {
data.fetchAttributes.then(result => {
attributes.value = result || []
})
} else {
attributes.value = data.fetchAttributes || []
} }
} }
// 分类相关状态 // 分类相关状态
const CategoryModal = async (data: any) => { const CategoryModal = async (data: any) => {
if (data.ifmodal) { showCategoryModal.value = data.ifmodal
openCategoryModal()
// console.log('打开分类蒙板')
} else {
closeCategoryModal()
// console.log('关闭分类蒙板')
}
} }
// 标签相关状态 // 标签相关状态
const AttributeModal = async (data: any) => { const AttributeModal = async (data: any) => {
if (data.ifmodal) { showAttributeModal.value = data.ifmodal
openAttributeModal()
// console.log('打开标签蒙板')
} else {
closeAttributeModal()
// console.log('关闭标签蒙板')
}
} }
// ========== 蒙版事件 ========== // ========== 蒙版事件 ==========
@@ -262,6 +236,11 @@ const handleAttributeClick = (attribute: any) => {
closeAttributeModal() closeAttributeModal()
} }
// 获取所有标签,扁平化处理
const getAllAttributes = () => {
return Categoriestree.value.flatMap(category => category.children || [])
}
// ========== 打字机效果模块 ========== // ========== 打字机效果模块 ==========
/** /**
@@ -580,7 +559,7 @@ const removeGlobalStoreValue = () => {
globalStore.removeValue('articlestatus'); globalStore.removeValue('articlestatus');
globalStore.removeValue('attribute'); globalStore.removeValue('attribute');
} }
// 不在article或者不在articlesave移除文章全局状态值 // 不在article或者不在articlesave移除文章全局状态值
if (rpsliturl[1] !== 'article' && rpsliturl[1] !== 'articlesave') { if (rpsliturl[1] !== 'article' && rpsliturl[1] !== 'articlesave') {
globalStore.removeValue('articleInfo'); globalStore.removeValue('articleInfo');
} }

View File

@@ -6,10 +6,8 @@ import NonsensePage from '../views/nonsense.vue'
import MessageBoardPage from '../views/messageboard.vue' import MessageBoardPage from '../views/messageboard.vue'
import AboutMePage from '../views/aboutme.vue' import AboutMePage from '../views/aboutme.vue'
import ArticleContentPage from '../views/articlecontents.vue' import ArticleContentPage from '../views/articlecontents.vue'
import LoginPage from '../views/login.vue'
import ArticleSavePage from '../views/articlesave.vue' import ArticleSavePage from '../views/articlesave.vue'
// 导入全局状态管理 import LoginPage from '../views/login.vue'
import { useGlobalStore } from '@/store/globalStore'
// 导入Element Plus消息组件 // 导入Element Plus消息组件
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
@@ -149,31 +147,30 @@ router.beforeEach((to, from, next) => {
document.title = '个人博客' document.title = '个人博客'
} }
// 获取全局状态 // 从localStorage获取token和登录状态
const globalStore = useGlobalStore()
// 从localStorage获取token
const token = localStorage.getItem('token') const token = localStorage.getItem('token')
const savedSpecificData = localStorage.getItem('globalStoreSpecificData')
const initialSpecificData = savedSpecificData ? JSON.parse(savedSpecificData) : {}
// 如果有token但登录状态为false自动恢复登录状态 // 检查token是否有效
if (token && !globalStore.Login && !isTokenExpired(token)) { const isTokenValid = token && !isTokenExpired(token)
globalStore.setLoginStatus(true)
}
// 如果有token但已过期清除token和登录状态 // 如果有token但已过期清除token和登录状态
if (token && isTokenExpired(token)) { if (token && isTokenExpired(token)) {
localStorage.removeItem('token') localStorage.removeItem('token')
globalStore.setLoginStatus(false) // 直接更新localStorage中的登录状态
initialSpecificData.Login = false
localStorage.setItem('globalStoreSpecificData', JSON.stringify(initialSpecificData))
ElMessage.error('登录已过期,请重新登录') ElMessage.error('登录已过期,请重新登录')
} }
// 检查是否需要认证 // 检查是否需要认证
if (to.meta.requiresAuth) { if (to.meta.requiresAuth) {
// 如果已登录,允许访问 // 如果token有效,允许访问
if (globalStore.Login) { if (isTokenValid) {
next() next()
} else { } else {
// 未登录,重定向到登录页 // 未登录或token已过期,重定向到登录页
ElMessage.warning('请先登录') ElMessage.warning('请先登录')
next('/login') next('/login')
} }

View File

@@ -4,7 +4,7 @@ import { ElMessage } from 'element-plus'
// 创建axios实例 // 创建axios实例
const api = axios.create({ const api = axios.create({
baseURL: '/api', baseURL: 'http://localhost:7071/api',
timeout: 10000, // 请求超时时间 timeout: 10000, // 请求超时时间
withCredentials: true // 允许跨域请求携带凭证如cookies withCredentials: true // 允许跨域请求携带凭证如cookies
}) })

View File

@@ -1,6 +1,4 @@
import Login from '@/views/login.vue'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
/** /**
* 全局状态管理store * 全局状态管理store
* 提供全局的传值和获取值的功能 * 提供全局的传值和获取值的功能

View File

@@ -15,19 +15,19 @@
<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="categoriestree.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.Categoryid" class="category-group-container"> <div v-for="categoryGroup in categoriestree" :key="categoryGroup.id" class="category-group-container">
<div v-if="categoryGroup.attributes.length > 0 && categoryGroup.attributes.some(cat => cat.articles && cat.articles.length > 0)"> <div v-if="calculateTotalArticles(categoryGroup.children) > 0">
<h2 id="header-id-1">{{ categoryGroup.typename }}</h2> <h2 id="header-id-1">{{ categoryGroup.name }}</h2>
<!-- 计算该分类组中实际有文章的属性数量 --> <span class="badge badge-primary"> {{ calculateTotalArticles(categoryGroup.children) }} </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">
<div v-for="attribute in categoryGroup.attributes" :key="attribute.attributeid"> <div v-for="attribute in categoryGroup.children" :key="attribute.id">
<li v-if="attribute.articles && attribute.articles.length > 0"> <li v-if="attribute.articlecount && attribute.articlecount > 0">
&nbsp;&nbsp;<a class="category-link" @click="handleCategoryClick(attribute)"><kbd>{{ attribute.attributename}}</kbd></a> &nbsp;&nbsp;<a class="category-link" @click="handleCategoryClick(attribute)"><kbd>{{
&nbsp; ({{ attribute.articles.length }}) attribute.attributename}}</kbd></a>
&nbsp; ({{ attribute.articlecount }})
</li> </li>
</div> </div>
</ul> </ul>
@@ -50,14 +50,14 @@ import { ref, onMounted } from 'vue'
// 由于模块 '@/services' 没有导出的成员 'CategoryService',请确认正确的导出名称后修改此处 // 由于模块 '@/services' 没有导出的成员 'CategoryService',请确认正确的导出名称后修改此处
// 以下代码仅作示例,实际需根据 '@/services' 模块的真实导出调整 // 以下代码仅作示例,实际需根据 '@/services' 模块的真实导出调整
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { categoryService, categoryAttributeService, articleService } from '@/services' import { categoryService } from '@/services'
import { useGlobalStore } from '@/store/globalStore' import { useGlobalStore } from '@/store/globalStore'
const globalStore = useGlobalStore() const globalStore = useGlobalStore()
const router = useRouter() const router = useRouter()
// 响应式状态 // 响应式状态
const categories = ref<any[]>([]) const categoriestree = ref<any[]>([])
const loading = ref(false) const loading = ref(false)
const error = ref('') const error = ref('')
@@ -67,46 +67,44 @@ const error = ref('')
const fetchCategories = async () => { const fetchCategories = async () => {
try { try {
loading.value = true; loading.value = true;
const res = await categoryService.getAllCategories(); const res = await categoryService.getCategoryTree();
let processedCategories: any[] = []; if (res.code !== 200) {
if (res.code === 200) { return []
processedCategories = res.data.map(item => ({ }
...item,
attributes: [] // 使用更清晰的命名
}));
// 使用Promise.all等待所有异步操作完成 categoriestree.value = res.data;
await Promise.all( // console.log('获取分类列表成功:', categoriestree.value);
processedCategories.map(async category => {
const attributes = await categoryAttributeService.getAttributesByCategory(category.categoryid);
if (attributes.code === 200 && Array.isArray(attributes.data)) {
const processedAttributes = await Promise.all(
attributes.data.map(async item => {
const articleItem = {
...item,
articles: []
};
const articlesRes = await articleService.getArticlesByAttributeId(item.attributeid);
if(articlesRes.code === 200 && Array.isArray(articlesRes.data)){
articleItem.articles = articlesRes.data;
}
return articleItem;
})
);
category.attributes = processedAttributes;
}
})
);
}
categories.value = processedCategories;
// console.log('获取分类列表成功:', categories.value);
} catch (err) { } catch (err) {
console.error('获取分类列表失败:', err); console.error('获取分类列表失败:', err);
ElMessage.error('获取分类列表失败,请稍后重试'); ElMessage.error('获取分类列表失败,请稍后重试');
}finally{ } finally {
loading.value = false; loading.value = false;
} }
} }
/**
* 过滤出有文章的分类组
* @param {any[]} categoryGroups - 分类组数组
* @returns {any[]} - 有文章的分类组数组
*/
const filterCategoriesWithArticles = (categoryGroups: any[]) => {
return categoryGroups.filter(group =>
group.children.some(child => child.articlecount && child.articlecount > 0)
)
}
/**
* 计算文章总数
* @param {any[]} categoryGroups - 分类组数组
* @returns {number} - 文章总数
*/
const calculateTotalArticles = (categoryGroups: any[]) => {
let TotalArticles = 0
if (categoryGroups.length > 0) {
categoryGroups.forEach(group => {
TotalArticles += group.articlecount
})
}
return TotalArticles
}
/** /**
* 处理分类点击事件 * 处理分类点击事件
* 注意现在实际上使用的是属性ID而不是分类ID * 注意现在实际上使用的是属性ID而不是分类ID
@@ -124,20 +122,7 @@ const handleCategoryClick = (attribute: any) => {
}) })
} }
/**
* 计算分类组中的文章总数
* @param {Array} categoryItems - 分类项数组
* @returns {number} 文章总数
*/
const getCategorySum = (categoryItems: any[]): number => {
if (!categoryItems || !Array.isArray(categoryItems)) {
return 0
}
return categoryItems.reduce((total, item) => {
return total + (item.sum || 0)
}, 0)
}
/** /**
* 组件挂载时获取分类列表 * 组件挂载时获取分类列表

View File

@@ -102,17 +102,12 @@ const loadNonsenseList = async () => {
pageNum: pageNum.value - 1, pageNum: pageNum.value - 1,
pageSize: pageSize.value pageSize: pageSize.value
}) })
// console.log(response)
if (response.code === 200) { if (response.code === 200) {
nonsenseList.value = response.data nonsenseList.value = response.data
// 计算总页数 // 计算总页数
totalPages.value = response.totalPages totalPages.value = response.totalPages
// 显示当前页的内容 // 显示当前页的内容
displayedNonsenseList.value = response.data displayedNonsenseList.value = response.data
// 初始化字符样式
// initializeCharStyles()
// 开始颜色变化定时器
// console.log(displayedNonsenseList.value)
} else { } else {
ElMessage.error('加载吐槽内容失败') ElMessage.error('加载吐槽内容失败')
error.value = true error.value = true