refactor(store): 移除未使用的Login导入并优化全局状态管理
refactor(router): 重构路由守卫逻辑,简化登录状态检查 refactor(components): 重构LeftModule组件,使用分类树数据并移除冗余代码 refactor(layouts): 优化MainLayout组件,使用分类树数据并简化模态框逻辑 refactor(views): 重构aericlelist视图,使用分类树数据并优化文章计数逻辑
This commit is contained in:
@@ -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() // 组件挂载时获取标签数据和分类数据
|
||||
})
|
||||
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import Login from '@/views/login.vue'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
/**
|
||||
* 全局状态管理store
|
||||
* 提供全局的传值和获取值的功能
|
||||
|
||||
@@ -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">
|
||||
<a class="category-link" @click="handleCategoryClick(attribute)"><kbd>{{ attribute.attributename}}</kbd></a>
|
||||
— —({{ attribute.articles.length }})
|
||||
<div v-for="attribute in categoryGroup.children" :key="attribute.id">
|
||||
<li v-if="attribute.articlecount && attribute.articlecount > 0">
|
||||
<a class="category-link" @click="handleCategoryClick(attribute)"><kbd>{{
|
||||
attribute.attributename}}</kbd></a>
|
||||
— —({{ 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)
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件挂载时获取分类列表
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user