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>
import { reactive, ref, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import { articleService, categoryService, categoryAttributeService } from "@/services";
import { categoryService } from "@/services";
// 当前激活菜单
const activeIndex = ref('/:type')
@@ -92,6 +92,9 @@ const state = reactive({
// 分类相关状态
// defineEmits
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) => {
activeIndex.value = to.path
})
// 文章数量状态
const articleCount = ref(0)
// 分类数量状态
const categoryCount = ref(0)
// 标签数量状态
const AttributeCount = ref(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 traverseCategoryTree = (tree) => {
categoryCount.value = tree.length
tree.forEach((category) => {
category.children.forEach((child) => {
articleCount.value += child.articlecount || 0
})
AttributeCount.value += category.children.length || 0
})
}
}
// 获取分类数据
const fetchCategories = async () => {
const fetchCategoriestree = async () => {
// 分类数据状态
const categories = ref<any[]>([])
try {
const response = await categoryService.getAllCategories();
const response = await categoryService.getCategoryTree()
// 如果API返回的数据结构不包含count属性我们可以模拟一些数据
categories.value = response.data?.map((category: any) => ({
...category,
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
traverseCategoryTree(response.data || [])
return response.data || []
} catch (error) {
console.error('获取分类失败:', error)
// 如果API调用失败使用模拟数据
categories.value = [
];
categoryCount.value = categories.value.length
return []
}
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 data = { fetchAttributes: fetchAttributes(), fetchCategories: fetchCategories() }
const data = { fetchCategoriestree: fetchCategoriestree() }
emit('update-data', data)
}
@@ -206,7 +158,6 @@ const handleScroll = () => {
// 生命周期管理事件监听,防止内存泄漏
onMounted(() => {
window.addEventListener('scroll', handleScroll)
fetchArticleCount() // 组件挂载时获取文章数量
sendData() // 组件挂载时获取标签数据和分类数据
})

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,21 +15,21 @@
<el-button type="primary" @click="fetchCategories">重新加载</el-button>
</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>
<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-if="categoryGroup.attributes.length > 0 && categoryGroup.attributes.some(cat => cat.articles && cat.articles.length > 0)">
<h2 id="header-id-1">{{ categoryGroup.typename }}</h2>
<!-- 计算该分类组中实际有文章的属性数量 -->
<span class="badge badge-primary"> {{ categoryGroup.attributes.reduce((total, cat) => total + (cat.articles && cat.articles.length ? cat.articles.length : 0), 0) }} </span>
<div v-for="categoryGroup in categoriestree" :key="categoryGroup.id" class="category-group-container">
<div v-if="calculateTotalArticles(categoryGroup.children) > 0">
<h2 id="header-id-1">{{ categoryGroup.name }}</h2>
<span class="badge badge-primary"> {{ calculateTotalArticles(categoryGroup.children) }} </span>
<ul class="category-item-list">
<div v-for="attribute in categoryGroup.attributes" :key="attribute.attributeid">
<li v-if="attribute.articles && attribute.articles.length > 0">
&nbsp;&nbsp;<a class="category-link" @click="handleCategoryClick(attribute)"><kbd>{{ attribute.attributename}}</kbd></a>
&nbsp; ({{ attribute.articles.length }})
<div v-for="attribute in categoryGroup.children" :key="attribute.id">
<li v-if="attribute.articlecount && attribute.articlecount > 0">
&nbsp;&nbsp;<a class="category-link" @click="handleCategoryClick(attribute)"><kbd>{{
attribute.attributename}}</kbd></a>
&nbsp; ({{ attribute.articlecount }})
</li>
</div>
</div>
</ul>
</div>
@@ -40,7 +40,7 @@
<div v-else class="empty-state-container">
<el-empty description="暂无分类" />
</div>
</div>
</template>
@@ -50,14 +50,14 @@ import { ref, onMounted } from 'vue'
// 由于模块 '@/services' 没有导出的成员 'CategoryService',请确认正确的导出名称后修改此处
// 以下代码仅作示例,实际需根据 '@/services' 模块的真实导出调整
import { ElMessage } from 'element-plus'
import { categoryService, categoryAttributeService, articleService } from '@/services'
import { categoryService } from '@/services'
import { useGlobalStore } from '@/store/globalStore'
const globalStore = useGlobalStore()
const router = useRouter()
// 响应式状态
const categories = ref<any[]>([])
const categoriestree = ref<any[]>([])
const loading = ref(false)
const error = ref('')
@@ -67,46 +67,44 @@ const error = ref('')
const fetchCategories = async () => {
try {
loading.value = true;
const res = await categoryService.getAllCategories();
let processedCategories: any[] = [];
if (res.code === 200) {
processedCategories = res.data.map(item => ({
...item,
attributes: [] // 使用更清晰的命名
}));
// 使用Promise.all等待所有异步操作完成
await Promise.all(
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;
}
})
);
const res = await categoryService.getCategoryTree();
if (res.code !== 200) {
return []
}
categories.value = processedCategories;
// console.log('获取分类列表成功:', categories.value);
categoriestree.value = res.data;
// console.log('获取分类列表成功:', categoriestree.value);
} catch (err) {
console.error('获取分类列表失败:', err);
ElMessage.error('获取分类列表失败,请稍后重试');
}finally{
} finally {
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
@@ -115,8 +113,8 @@ const fetchCategories = async () => {
const handleCategoryClick = (attribute: any) => {
globalStore.removeValue('attribute')
globalStore.setValue('attribute', {
id: attribute.attributeid,
name: attribute.attributename
id: attribute.attributeid,
name: attribute.attributename
})
router.push({
path: '/home/aericleattribute',
@@ -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,
pageSize: pageSize.value
})
// console.log(response)
if (response.code === 200) {
nonsenseList.value = response.data
// 计算总页数
totalPages.value = response.totalPages
// 显示当前页的内容
displayedNonsenseList.value = response.data
// 初始化字符样式
// initializeCharStyles()
// 开始颜色变化定时器
// console.log(displayedNonsenseList.value)
} else {
ElMessage.error('加载吐槽内容失败')
error.value = true