diff --git a/src/components/LeftModule.vue b/src/components/LeftModule.vue index c4699e0..5c15f9d 100644 --- a/src/components/LeftModule.vue +++ b/src/components/LeftModule.vue @@ -20,7 +20,7 @@ 首页 - + 目录 @@ -51,13 +51,13 @@
- + {{ categoryCount }} 分类
- + {{ AttributeCount }} 标签 @@ -68,6 +68,50 @@
+ + + +
+
+
+

所有分类

+ +
+
+ +
+
+
+
+ + + +
+
+
+

所有标签

+ +
+
+ +
+
+
+
@@ -75,6 +119,8 @@ import { reactive, ref, onMounted, onUnmounted } from 'vue' import { useRouter } from 'vue-router' import { articleService, categoryService, categoryAttributeService } from "@/services"; +import { useGlobalStore } from '@/store/globalStore' +const globalStore = useGlobalStore() // 当前激活菜单 const activeIndex = ref('/:type') @@ -86,6 +132,13 @@ const state = reactive({ sizeList: ['small', '', 'large'] as const, }) +// 分类相关状态 +const categories = ref([]) +const showCategoryModal = ref(false) + +// 标签相关状态 +const attributes = ref([]) +const showAttributeModal = ref(false) // 处理菜单选择跳转 const handleSelect = (key: string) => { @@ -109,40 +162,110 @@ const fetchArticleCount = async () => { try { const response = await articleService.getAllArticles(); articleCount.value = response.data?.length || 0 - // 这里应该调用API获取实际的文章数量 - // 暂时设置为模拟数据 } catch (error) { console.error('获取文章数量失败:', error) articleCount.value = 0 } } -// 获取分类数量 -const fetchCategoryCount = async () => { +// 获取分类数据 +const fetchCategories = async () => { try { - const response = await categoryService.getAllCategories(); - categoryCount.value = response.data?.length || 0 - // 这里应该调用API获取实际的分类数量 - // 暂时设置为模拟数据 + const response = await categoryService.getAllCategories(); + // 如果API返回的数据结构不包含count属性,我们可以模拟一些数据 + 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) { - console.error('获取分类数量失败:', error) - categoryCount.value = 0 + + console.error('获取分类失败:', error) + // 如果API调用失败,使用模拟数据 + categories.value = [ + + ]; + categoryCount.value = categories.value.length } } -// 获取标签数量 -const fetchAttributeCount = async () => { +// 获取标签数据 +const fetchAttributes = async () => { try { - const response = await categoryAttributeService.getAllAttributes(); - AttributeCount.value = response.data?.length || 0 - // 这里应该调用API获取实际的标签数量 - // 暂时设置为模拟数据 + 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) - AttributeCount.value = 0 + console.error('获取标签失败:', error) + // 如果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 handleScroll = () => { @@ -153,8 +276,8 @@ const handleScroll = () => { onMounted(() => { window.addEventListener('scroll', handleScroll) fetchArticleCount() // 组件挂载时获取文章数量 - fetchCategoryCount() // 组件挂载时获取分类数量 - fetchAttributeCount() // 组件挂载时获取标签数量 + fetchCategories() // 组件挂载时获取分类数据 + fetchAttributes() // 组件挂载时获取标签数据 }) onUnmounted(() => { @@ -190,10 +313,17 @@ onUnmounted(() => { /* 内容区域样式 */ #cont { + padding:0 0 10px 0; border-radius: 10px; background-color: rgba(255, 255, 255, 0.9); /* 白色半透明背景 */ } +#cont .cont1{ + margin-bottom: 5px; +} +#cont .cont2{ + margin-bottom: 0px; +} .cont1 { text-align: center; @@ -213,18 +343,19 @@ onUnmounted(() => { } /* 菜单样式 */ -.cont2 { - margin-top: 20px; -} - .cont2 .el-menu-vertical-demo { display: block; 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) { - border-radius: 0 0 10px 10px; + /* border-radius: 0 0 10px 10px; */ + /* margin-bottom: 10px; */ } .cont2 .el-menu-vertical-demo .el-menu-item:hover { @@ -376,4 +507,141 @@ onUnmounted(() => { 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; +} \ No newline at end of file diff --git a/src/layouts/MainLayout.vue b/src/layouts/MainLayout.vue index b5880e8..3573e4f 100644 --- a/src/layouts/MainLayout.vue +++ b/src/layouts/MainLayout.vue @@ -12,7 +12,7 @@ 首页 - + 目录 @@ -210,7 +210,7 @@ const performSearch = () => { * 根据路由路径设置页面状态 */ const updatePageState = () => { - if (rpsliturl[1] === localhome && rpsliturl[2] == undefined) { + if (rpsliturl[1] == localhome && rpsliturl[2] == undefined) { classhero.value = false; } else { classhero.value = true; @@ -225,7 +225,6 @@ const setActiveIndex = (path: string) => { globalStore.setValue('localpath', { name: path }) - // console.log('设置激活索引:', path); if (path === 'message') { globalStore.removeValue('articleInfo') } diff --git a/src/layouts/establish.vue b/src/layouts/establish.vue index 8f259f4..4071dab 100644 --- a/src/layouts/establish.vue +++ b/src/layouts/establish.vue @@ -39,7 +39,9 @@ const isNonsenseModalVisible = ref(false) const nonsenseContent = ref('') // 基础按钮配置 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: [ { 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: 'published-articles', label: '已发表', icon: 'icon-new-tag' }, ...baseButtons ], @@ -66,7 +69,7 @@ const pageButtons = { ], // 分类页面按钮 - category: [ + articlelist: [ { id: 'create-category', label: '新建', icon: 'icon-create-category' }, ...baseButtons ], @@ -86,6 +89,21 @@ const pageButtons = { // 默认按钮 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 = () => { @@ -151,10 +169,51 @@ const handleErrorResponse = (error, defaultMessage = '操作失败') => { console.error('操作失败:', error) 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 = () => { - if (!route.query.id) { + const articleId = globalStore.getValue('articleInfo')?.id + if (!articleId) { ElMessage.warning('缺少文章ID参数') return } @@ -165,7 +224,7 @@ const deleteArticle = () => { type: 'warning' }).then(() => { // 调用删除文章接口 - articleService.deleteArticle(Number(route.query.id)) + articleService.deleteArticle(Number(articleId)) .then(response => { if (response.code === 200) { 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 = () => { @@ -200,7 +278,11 @@ const logout = () => { }) .catch(err => handleErrorResponse(err, '登出失败')) } +// 刷新页面 +const reloadPage = () => { + globalStore.clearAll() +} // 处理按钮点击事件 const handleButtonClick = (button) => { console.log('点击了按钮:', button.id, button.label) @@ -209,11 +291,12 @@ const handleButtonClick = (button) => { switch (button.id) { // 新增操作 case 'create-article': - // router.push({ path: '/articlesave' }) + // 清除更新文章状态 + router.push({ path: '/articlesave' }) break case 'create-category': - // router.push({ path: '/categorysave' }) + createCategory() break case 'create-tag': @@ -227,27 +310,25 @@ const handleButtonClick = (button) => { // 修改操作 case 'edit-article': - if (route.query.id) { - globalStore.setValue('localpath', { name: 'article', id: Number(route.query.id) }) - router.push({ path: '/articlesave', query: { id: route.query.id } }) - } else { - ElMessage.warning('缺少文章ID参数') - } + updateArticle() break // 删除操作 case 'delete-article': deleteArticle(); break - break // 查看操作 - case 'published-articles': - router.push({ path: '/home', query: { status: 1 } }) + case 'del-articles': + getButtonsByStatus(2) break case 'unpublished-articles': - router.push({ path: '/home', query: { status: 0 } }) + getButtonsByStatus(0) + break + + case 'published-articles': + getButtonsByStatus(1) break case 'view-articles': @@ -259,6 +340,10 @@ const handleButtonClick = (button) => { logout(); break + case 'reload': + reloadPage() + break + default: console.warn('未处理的按钮类型:', button.id, button.label) ElMessage.info(`功能 ${button.label} 暂未实现`) diff --git a/src/router/Router.js b/src/router/Router.js index c3f3f91..3bd7137 100644 --- a/src/router/Router.js +++ b/src/router/Router.js @@ -26,16 +26,24 @@ const routes = [ children: [ { path: 'aericletype', - name: 'homeByType' + name: 'homeByType', + component: HomePage }, { path: 'aericletitle', - name: 'homeByTitle' + name: 'homeByTitle', + component: HomePage + }, + { + path: 'aericlestatus', + name: 'homeByStatus', + component: HomePage } + ] }, { - path: '/article-list', + path: '/articlelist', name: 'articleList', component: ArticleList, meta: { diff --git a/src/services/articleService.js b/src/services/articleService.js index cfe4fad..fd3aa42 100644 --- a/src/services/articleService.js +++ b/src/services/articleService.js @@ -13,7 +13,22 @@ class ArticleService { getAllArticles(params = {}) { return api.get('/articles/published', { params }) } - + /** + * 根据状态获取文章列表 + * @param {number} status - 文章状态(0:未发表 1:已发表 2:已删除) + * @returns {Promise>} + */ + getArticlesByStatus(status) { + return api.get(`/articles/status/${status}`) + } + /** + * 获取所有文章列表(包含已删除) + * @param {import('../types').PaginationParams} params - 查询参数 + * @returns {Promise>} + */ + getAllArticlesWithDeleted(params = {}) { + return api.get('/articles', { params }) + } /** * 根据ID获取文章详情 * @param {number} articleid - 文章ID diff --git a/src/services/nonsenseService.js b/src/services/nonsenseService.js index 1f8c6a9..43bc451 100644 --- a/src/services/nonsenseService.js +++ b/src/services/nonsenseService.js @@ -3,31 +3,39 @@ import apiService from './apiService' class NonsenseService { /** - * 获取所有随机内容 + * 获取所有疯言疯语内容 * @returns {Promise>} */ getAllNonsense() { return apiService.get('/nonsense') } /** - * 保存随机内容 - * @param {import('../types').Nonsense} nonsense - 随机内容对象 + * 根据状态获取疯言疯语内容 + * @param {number} status - 状态值(1:已发表, 0:草稿) + * @returns {Promise>} + */ + getNonsenseByStatus(status){ + return apiService.get(`/nonsense/status/${status}`) + } + /** + * 保存疯言疯语内容 + * @param {import('../types').Nonsense} nonsense - 疯言疯语内容对象 * @returns {Promise>} */ saveNonsense(nonsense){ return apiService.post('/nonsense', nonsense) } /** - * 删除随机内容 - * @param {number} id - 随机内容ID + * 删除疯言疯语内容 + * @param {number} id - 疯言疯语内容ID * @returns {Promise>} */ deleteNonsense(id){ return apiService.delete(`/nonsense/${id}`) } /** - * 更新随机内容 - * @param {import('../types').Nonsense} nonsense - 随机内容对象 + * 更新疯言疯语内容 + * @param {import('../types').Nonsense} nonsense - 疯言疯语内容对象 * @returns {Promise>} */ updateNonsense(nonsense){ diff --git a/src/styles/MainLayout.css b/src/styles/MainLayout.css index 64eb43c..e1973f0 100644 --- a/src/styles/MainLayout.css +++ b/src/styles/MainLayout.css @@ -129,6 +129,90 @@ p { font-weight: 300; 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 { diff --git a/src/types/index.ts b/src/types/index.ts index ada518d..66415b1 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -14,6 +14,7 @@ export interface Article { updatedAt: string viewCount?: number likes?: number + commentCount?: number status?: number markdownscontent: string } @@ -89,6 +90,7 @@ export interface CategoryDto { * 分类属性接口 */ export interface CategoryAttribute { + map(arg0: (item: any) => any): unknown attributeid: number categoryid: number attributename: string @@ -127,7 +129,23 @@ export interface UserDto { phone: string role?: number } - +/** + * 疯言疯语类型接口 + */ +export interface Nonsense { + nonsenseid: number + content: string + status?: number + time: string +} +/** + * 疯言疯语DTO接口 + */ +export interface NonsenseDto { + content: string + status?: number + time?: string +} /** * API响应接口 */ diff --git a/src/views/aboutme.vue b/src/views/aboutme.vue index 983159d..8c2e6cb 100644 --- a/src/views/aboutme.vue +++ b/src/views/aboutme.vue @@ -15,11 +15,11 @@ 好像真被一阵风吹散了似的, 哈哈哈哈哈,我还蛮喜欢这个外号的。

-     后来有一天,也不知道怎么了,精神状态不是很好,感觉要控制不住要发疯了,突然这疯子就像钉子一样扎在我脑海里(哈哈哈哈!)。于是就干脆改名叫“清疯”了——清风,清疯,念起来几乎没差, +     有段时间,不知道怎么了,精神状态不是很好,好想发疯,突然这疯就像钉子一样扎在我脑海里(哈哈哈哈!)。于是就干脆改名叫“清疯”了——清风,清疯,念起来几乎没差, 但内核却从一种理想的淡然,切换成了真实的、带点毛边的鲜活。“悠悠清风荡我心”,只是如今这阵清风熬成了清疯,终于在我心里刮起一场疯——疯啦!

-     又过些日子,玩新游戏要起名,正盯着输入框发呆,“清疯”两个字在脑海里冒出来……疯癫?哎,我好像是疯,但也没完全颠嘛!那种在理智边界试探、却绝不越线的微妙感,一下对味了——干脆就叫“清疯不颠”! - 名字一出自己先乐了,这不就是我吗,表面疯癫,内心门儿清,简直是我们这代人精神状态的绝佳注脚。 +     又过些日子,玩新游戏要起名,正盯着输入框里的“清疯”发呆, 两个字在脑海里冒出来……疯癫?哎,好像是疯了,但也没完全颠嘛!那种在理智边界试探、却绝不越线的微妙感,一下对味了——干脆就叫“清疯不颠”! + 名字一出自己先乐了。

哈哈哈哈哈哈!俗话说天才在左疯子在右,在我这儿,大概是左脑负责疯,右脑负责颠,两个家伙吵吵闹闹,反而让我在这个世界里自得其乐。 @@ -28,7 +28,7 @@

疯言疯语

-     我并没有网站的开发经验这是我的第一个项目,所以我并不清楚该如何去写这个页面。就干脆当一个我发疯的地方吧(哈哈哈哈哈 所以你有看到彩色的小鹿吗?) +     我并没有网站的开发经验这是我的第一个项目,我想设计一下独属于清疯的页面,可是我并不清楚该如何去写这个页面。就干脆当一个我发疯的地方吧(哈哈哈哈哈 所以你有看到彩色的小鹿吗?)
@@ -96,7 +96,7 @@

联系方式

-

如果你有任何问题或建议,欢迎随时联系我!

+

如果你有任何问题或建议,欢迎随时联系我!(我文笔真的很烂QAQ)

留言板
diff --git a/src/views/aericlelist.vue b/src/views/aericlelist.vue index 6244c17..3710222 100644 --- a/src/views/aericlelist.vue +++ b/src/views/aericlelist.vue @@ -6,7 +6,7 @@
- +
@@ -14,20 +14,19 @@ 重新加载
-

-
文章分类如下,点击跳转
-
-
+
文章分类如下,点击跳转
+
+

{{ categoryGroup.typename }}

- 共 {{ categoryGroup.attributes.length }} 篇 + + 共 {{ categoryGroup.attributes.reduce((total, cat) => total + (cat.articles && cat.articles.length ? cat.articles.length : 0), 0) }} 篇 @@ -66,90 +65,61 @@ const error = ref('') const fetchCategories = async () => { try { loading.value = true; - error.value = ''; - - const res = await categoryService.getAllCategories(); - - if (res.data && res.data.length > 0) { - // 创建处理后的分类数组 - const processedCategories = res.data.map(item => ({ + 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) => { - try { - if (await categoryAttributeService.checkAttributeExists(category.typeid, category.typename || '')) { - // 获取分类的所有属性 - const attributesRes = await categoryAttributeService.getAttributeById(category.typeid); - // 存储属性数据 - if (attributesRes.data) { - category.attributes = Array.isArray(attributesRes.data) ? attributesRes.data : [attributesRes.data]; - } - } - } catch (err) { - // console.error(`处理分类失败 (分类ID: ${category.typeid}):`, err); + processedCategories.map(async category => { + const attributes = await categoryAttributeService.getAttributesByCategory(category.typeid); + 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; } }) ); - await getCategoryAttributes(processedCategories); - console.log('获取分类列表成功:', categories.value); - } else { - categories.value = []; } + categories.value = processedCategories; + console.log('获取分类列表成功:', categories.value); } catch (err) { - error.value = '获取分类列表失败,请稍后重试'; console.error('获取分类列表失败:', err); - ElMessage.error(error.value); - } finally { + ElMessage.error('获取分类列表失败,请稍后重试'); + }finally{ 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 - * @param {string | number} attributeId - 属性ID - */ +* 处理分类点击事件 +* 注意:现在实际上使用的是属性ID而不是分类ID +* @param {string | number} attributeId - 属性ID +*/ const handleCategoryClick = (attribute: any) => { + globalStore.removeValue('attribute') globalStore.setValue('attribute', { - id: attribute.typeid || attribute.categoryid, - name: attribute.attributename || attribute.typename || '未命名属性', + id: attribute.attributeid, + name: attribute.typename }) console.log(attribute) router.push({ path: '/home/aericletype', + }) } diff --git a/src/views/articlecontents.vue b/src/views/articlecontents.vue index e281452..12c8715 100644 --- a/src/views/articlecontents.vue +++ b/src/views/articlecontents.vue @@ -19,18 +19,16 @@

{{ article.title }}

@@ -85,12 +83,10 @@ // 导入必要的依赖 import { useRoute, useRouter } from 'vue-router' import { ref, onMounted } from 'vue' -import { articleService } from '@/services' -import { categoryAttributeService } from '@/services' import { useGlobalStore } from '@/store/globalStore' import { ElMessage } from 'element-plus' import type { Article } from '@/types' -import { formatDate } from '@/utils/dateUtils' +import { formatRelativeTime } from '@/utils/dateUtils' import messageboard from './messageboard.vue' import markdownViewer from './markdown.vue' // 路由相关 @@ -122,22 +118,19 @@ const fetchArticleDetail = async () => { if (response) { article.value = response // 获取并设置分类名称 - const categoryResponse = await categoryAttributeService.getAttributeById(Number(article.value.attributeid)) - article.value.categoryName = categoryResponse.data.attributename || '未分类' - - // 增加文章浏览量 - try { - await articleService.incrementArticleViews(Number(articleId)) - // 更新前端显示的浏览量 - if (article.value.viewCount) { - article.value.viewCount++ - } else { - article.value.viewCount = 1 - } - } catch (err) { - console.error('增加文章浏览量失败:', err) - // 不阻止主流程 + // const categoryResponse = await categoryAttributeService.getAttributeById(Number(article.value.attributeid)) + // article.value.categoryName = categoryResponse.data.attributename || '未分类' + // 获取并设置评论量 + // const commentResponse = await messageService.getMessagesByArticleId(articleId) + // article.value.commentCount = commentResponse.data.length || 0 + // 更新浏览量 + // 更新前端显示的浏览量 + if (article.value.viewCount) { + article.value.viewCount++ + } else { + article.value.viewCount = 1 } + } else { throw new Error('文章不存在或已被删除') } @@ -181,7 +174,7 @@ onMounted(() => { .article-detail-wrapper { max-width: 900px; margin: 0 auto; - background-color: rgba(255, 255, 255, 0.85); + background-color: rgba(255, 255, 255, 0.85); border-radius: 12px; padding: 40px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); @@ -193,7 +186,7 @@ onMounted(() => { max-width: 900px; margin: 0 auto; padding: 40px; - background-color: rgba(255, 255, 255, 0.85); + background-color: rgba(255, 255, 255, 0.85); border-radius: 12px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); } @@ -203,7 +196,7 @@ onMounted(() => { max-width: 900px; margin: 0 auto; padding: 60px 40px; - background-color: rgba(255, 255, 255, 0.85); + background-color: rgba(255, 255, 255, 0.85); border-radius: 12px; text-align: center; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); @@ -218,7 +211,7 @@ onMounted(() => { max-width: 900px; margin: 0 auto; padding: 80px 40px; - background-color: rgba(255, 255, 255, 0.85); + background-color: rgba(255, 255, 255, 0.85); border-radius: 12px; text-align: center; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); diff --git a/src/views/articlesave.vue b/src/views/articlesave.vue index 3d8d03b..7bc45d3 100644 --- a/src/views/articlesave.vue +++ b/src/views/articlesave.vue @@ -14,7 +14,7 @@ - + @@ -45,6 +45,8 @@ import 'md-editor-v3/lib/style.css'; import { categoryService, categoryAttributeService, articleService } from '@/services'; import type { Article } from '@/types/index.ts'; import { ElMessage } from 'element-plus'; +import { useGlobalStore } from '@/store/globalStore' +const globalStore = useGlobalStore() // 路由 import router from '@/router/Router'; const Articleform = ref
({ @@ -55,7 +57,8 @@ const Articleform = ref
({ categoryName: '', createdAt: '', updatedAt: '', - markdownscontent: '' + markdownscontent: '', + status: 0 // 默认状态为草稿 }) // 用于级联选择器的值绑定 @@ -63,16 +66,29 @@ const selectedValues = ref([]); const categorieoptions = ref([]); const statusoptions = ref([ { - label: '未发布', + label: '草稿', value: '0' }, { label: '发布', value: '1' + }, + { + label: '删除', + value: '2' } ]); 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 const loadCategories = async () => { try { @@ -107,7 +123,22 @@ const loadCategories = async () => { }) ); 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) { console.error('加载分类失败:', error); @@ -138,6 +169,7 @@ const handleSave = (markdown) => { // 构建请求数据 const articleData = { + articleid: Articleform.value.articleid, title: Articleform.value.title, content: Articleform.value.content, attributeid: Number(Articleform.value.attributeid), @@ -150,28 +182,37 @@ const handleSave = (markdown) => { console.log('发送文章数据:', articleData); console.log('当前认证token是否存在:', !!localStorage.getItem('token')); - // 保存文章 - articleService.createArticle(articleData) + // 根据articleid决定调用创建还是更新接口 + const savePromise = Articleform.value.articleid === 0 + ? articleService.createArticle(articleData) + : articleService.updateArticle(Articleform.value.articleid, articleData); + savePromise .then(res => { console.log('API响应:', res); if (res.code === 200) { - ElMessage.success('文章保存成功') - // 重置表单 - Articleform.value = { - articleid: 0, - title: '', - content: '', - attributeid: 0, - categoryName: '', - createdAt: '', - updatedAt: '', - markdownscontent: '' - }; - selectedValues.value = []; + ElMessage.success(Articleform.value.articleid === 0 ? '文章创建成功' : '文章更新成功'); + // 清除全局存储中的article + globalStore.removeValue('updatearticle'); + // 重置表单或保留编辑状态 + if (Articleform.value.articleid === 0) { + // 创建新文章后重置表单 + Articleform.value = { + articleid: 0, + title: '', + content: '', + attributeid: 0, + categoryName: '', + createdAt: '', + updatedAt: '', + markdownscontent: '' + }; + selectedValues.value = []; + } + // 返回列表页 router.push('/home'); } else { - ElMessage.error(res.message || '文章保存失败') + ElMessage.error(res.message || (Articleform.value.articleid === 0 ? '文章创建失败' : '文章更新失败')); } }) .catch(err => { @@ -181,17 +222,19 @@ const handleSave = (markdown) => { console.error('错误状态码:', err.response.status); console.error('错误响应数据:', err.response.data); + const operationType = Articleform.value.articleid === 0 ? '创建' : '更新'; + if (err.response.status === 401) { ElMessage.error('未授权访问,请先登录'); } else if (err.response.status === 403) { - ElMessage.error('没有权限创建文章,请检查账号权限'); + ElMessage.error(`没有权限${operationType}文章,请检查账号权限`); } else if (err.response.status === 400) { ElMessage.error('数据验证失败: ' + (err.response.data?.message || '请检查输入')); } else { - ElMessage.error('请求被拒绝,错误代码: ' + err.response.status); + ElMessage.error(`请求被拒绝,错误代码: ${err.response.status}`); } } else { - ElMessage.error(err.message || '文章保存失败') + ElMessage.error(err.message || (Articleform.value.articleid === 0 ? '文章创建失败' : '文章更新失败')); } }) }; diff --git a/src/views/home.vue b/src/views/home.vue index a315a1b..d5ef8d4 100644 --- a/src/views/home.vue +++ b/src/views/home.vue @@ -20,10 +20,15 @@

{{ formatContentPreview(article.content, 150) }}

@@ -36,7 +41,7 @@