From b8362e7835dc11cd77b652550d748639b08c8e75 Mon Sep 17 00:00:00 2001 From: qingfeng1121 Date: Sun, 12 Oct 2025 14:24:20 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E7=BB=93=E6=9E=84=E5=B9=B6=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=96=B0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构项目目录结构,将组件和服务模块化 添加Element Plus UI库并集成到项目中 实现文章、留言和分类的类型定义 新增工具函数模块包括日期格式化和字符串处理 重写路由配置并添加全局路由守卫 优化页面布局和响应式设计 新增服务层封装API请求 完善文章详情页和相关文章推荐功能 --- jsconfig.json | 1 + src/App.vue | 25 +- src/axios/api.js | 2 + src/components/LeftModule.vue | 240 +++++++ src/index.vue | 2 +- src/layouts/MainLayout.vue | 206 ++++++ src/main.js | 6 +- src/router/Router.js | 87 ++- src/services/apiService.js | 46 ++ src/services/articleService.js | 75 ++ src/services/index.js | 8 + src/services/messageService.js | 89 +++ .../index.css => styles/MainLayout.css} | 22 +- src/types/index.ts | 79 +++ src/utils/dateUtils.js | 90 +++ src/utils/stringUtils.js | 75 ++ src/views/aboutme.vue | 212 +++++- src/views/aericle.vue | 243 ++++++- src/views/articlecontents.vue | 503 ++++++++++++- src/views/home.vue | 245 ++++++- src/views/leftmodlue.vue | 212 ------ src/views/messageboard.vue | 658 +++++++++++++++--- 22 files changed, 2673 insertions(+), 453 deletions(-) create mode 100644 src/components/LeftModule.vue create mode 100644 src/layouts/MainLayout.vue create mode 100644 src/services/apiService.js create mode 100644 src/services/articleService.js create mode 100644 src/services/index.js create mode 100644 src/services/messageService.js rename src/{assets/index.css => styles/MainLayout.css} (92%) create mode 100644 src/types/index.ts create mode 100644 src/utils/dateUtils.js create mode 100644 src/utils/stringUtils.js delete mode 100644 src/views/leftmodlue.vue diff --git a/jsconfig.json b/jsconfig.json index 5a1f2d2..e0aad8a 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -1,4 +1,5 @@ { + "include": ["src/**/*"], "compilerOptions": { "paths": { "@/*": ["./src/*"] diff --git a/src/App.vue b/src/App.vue index ef9468d..30b53e6 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,9 +1,24 @@ - - diff --git a/src/axios/api.js b/src/axios/api.js index 06023f3..6c7fc45 100644 --- a/src/axios/api.js +++ b/src/axios/api.js @@ -83,4 +83,6 @@ export const messageAPI = { deleteMessage: (id) => service.delete(`/messages/${id}`) } + + export default service \ No newline at end of file diff --git a/src/components/LeftModule.vue b/src/components/LeftModule.vue new file mode 100644 index 0000000..c17d96d --- /dev/null +++ b/src/components/LeftModule.vue @@ -0,0 +1,240 @@ + + + + + \ No newline at end of file diff --git a/src/index.vue b/src/index.vue index cb5e5fd..76d2c28 100644 --- a/src/index.vue +++ b/src/index.vue @@ -54,7 +54,7 @@ + + \ No newline at end of file diff --git a/src/main.js b/src/main.js index a64334a..f7c133d 100644 --- a/src/main.js +++ b/src/main.js @@ -1,7 +1,11 @@ import { createApp } from 'vue' import App from './App.vue' import Router from './router/Router' -import './assets/index.css' +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import './styles/MainLayout.css' + const app = createApp(App) app.use(Router) +app.use(ElementPlus) app.mount('#app') \ No newline at end of file diff --git a/src/router/Router.js b/src/router/Router.js index 132c692..65200e5 100644 --- a/src/router/Router.js +++ b/src/router/Router.js @@ -1,54 +1,93 @@ import { createWebHistory, createRouter } from 'vue-router' -import Aericle from '../views/aericle.vue' -import home from '../views/home.vue' -import nonsense from '../views/nonsense.vue' -import messageboard from '../views/messageboard.vue' -import about from '../views/aboutme.vue' -import articlecontents from '../views/articlecontents.vue' +// 导入视图组件 +import ArticleList from '../views/aericle.vue' +import HomePage from '../views/home.vue' +import NonsensePage from '../views/nonsense.vue' +import MessageBoardPage from '../views/messageboard.vue' +import AboutMePage from '../views/aboutme.vue' +import ArticleContentPage from '../views/articlecontents.vue' + +/** + * 路由配置数组 + * 定义了应用的所有路由路径和对应的组件 + */ const routes = [ { path: '/', - redirect: '/:type' // 默认跳转到首页 + redirect: '/all' // 默认跳转到首页,显示所有文章 }, { path: '/:type', - // 如果type为空则是所有不为空查询相对应属性的文章 - name: '/home', - component: home + name: 'home', + component: HomePage, + meta: { + title: '首页' + } }, { - path: '/aericle', - name: 'Aericle', - component: Aericle + path: '/article-list', + name: 'articleList', + component: ArticleList, + meta: { + title: '文章目录' + } }, { path: '/nonsense', name: 'nonsense', - component: nonsense - + component: NonsensePage, + meta: { + title: '随笔' + } }, { path: '/message', - name: 'messageboard', - component: messageboard - + name: 'messageBoard', + component: MessageBoardPage, + meta: { + title: '留言板' + } }, { path: '/about', - name: 'about', - component: about - + name: 'aboutMe', + component: AboutMePage, + meta: { + title: '关于我' + } }, { - path: '/articlecontents/:url', - name: 'articlecontents', - component: articlecontents + path: '/article/:url', + name: 'articleContent', + component: ArticleContentPage, + meta: { + title: '文章详情' + } } ] +/** + * 创建路由实例 + */ const router = createRouter({ history: createWebHistory(), routes, + scrollBehavior() { + // 路由切换时滚动到页面顶部 + return { top: 0 } + } +}) + +/** + * 全局路由守卫 - 处理页面标题 + */ +router.beforeEach((to, from, next) => { + if (to.meta.title) { + document.title = to.meta.title + ' - 个人博客' + } else { + document.title = '个人博客' + } + next() }) export default router; \ No newline at end of file diff --git a/src/services/apiService.js b/src/services/apiService.js new file mode 100644 index 0000000..0fa017f --- /dev/null +++ b/src/services/apiService.js @@ -0,0 +1,46 @@ +// 基础 API 服务配置 +import axios from 'axios' + +// 创建 axios 实例 +const apiService = axios.create({ + baseURL: 'http://localhost:8080/api', // api的base_url + timeout: 10000, // 请求超时时间 + headers: { + 'Content-Type': 'application/json' + } +}) + +// 请求拦截器 - 添加认证token +apiService.interceptors.request.use( + config => { + const token = localStorage.getItem('token') + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + return config + }, + error => { + return Promise.reject(error) + } +) + +// 响应拦截器 - 统一处理响应 +apiService.interceptors.response.use( + response => { + // 检查响应是否成功 + if (response.data && response.data.success) { + return response.data + } else { + // 处理业务错误 + return Promise.reject(new Error(response.data?.message || '请求失败')) + } + }, + error => { + // 处理HTTP错误 + console.error('API请求错误:', error) + // 可以在这里添加全局错误处理,如显示错误提示 + return Promise.reject(error) + } +) + +export default apiService \ No newline at end of file diff --git a/src/services/articleService.js b/src/services/articleService.js new file mode 100644 index 0000000..b84d9da --- /dev/null +++ b/src/services/articleService.js @@ -0,0 +1,75 @@ +// 文章相关API服务 +import apiService from './apiService' + +/** + * 文章服务类 + */ +class ArticleService { + /** + * 获取所有文章 + * @param {Object} params - 查询参数 + * @returns {Promise} + */ + getAllArticles(params = {}) { + return apiService.get('/articles', { params }) + } + + /** + * 获取单篇文章 + * @param {number} id - 文章ID + * @returns {Promise} + */ + getArticleById(id) { + return apiService.get(`/articles/${id}`) + } + + + + /** + * 获取热门文章 + * @returns {Promise} + */ + getPopularArticles() { + return apiService.get('/articles/popular') + } + + /** + * 创建文章 + * @param {Object} articleData - 文章数据 + * @returns {Promise} + */ + createArticle(articleData) { + return apiService.post('/articles', articleData) + } + + /** + * 更新文章 + * @param {number} id - 文章ID + * @param {Object} articleData - 文章数据 + * @returns {Promise} + */ + updateArticle(id, articleData) { + return apiService.put(`/articles/${id}`, articleData) + } + + /** + * 删除文章 + * @param {number} id - 文章ID + * @returns {Promise} + */ + deleteArticle(id) { + return apiService.delete(`/articles/${id}`) + } + + /** + * 增加文章浏览量 + * @param {number} id - 文章ID + * @returns {Promise} + */ + incrementArticleViews(id) { + return apiService.post(`/articles/${id}/views`) + } +} + +// 导出文章服务实例 +export default new ArticleService() \ No newline at end of file diff --git a/src/services/index.js b/src/services/index.js new file mode 100644 index 0000000..d555208 --- /dev/null +++ b/src/services/index.js @@ -0,0 +1,8 @@ +// 导出所有服务 +import articleService from './articleService' +import messageService from './messageService' + +export { + articleService, + messageService +} \ No newline at end of file diff --git a/src/services/messageService.js b/src/services/messageService.js new file mode 100644 index 0000000..9f5464a --- /dev/null +++ b/src/services/messageService.js @@ -0,0 +1,89 @@ +// 留言相关API服务 +import apiService from './apiService' + +/** + * 留言服务类 + */ +class MessageService { + /** + * 获取所有留言 + * @returns {Promise} + */ + getAllMessages() { + return apiService.get('/messages') + } + + /** + * 获取单条留言 + * @param {number} id - 留言ID + * @returns {Promise} + */ + getMessageById(id) { + return apiService.get(`/messages/${id}`) + } + + /** + * 根据文章ID获取留言 + * @param {number} articleId - 文章ID + * @returns {Promise} + */ + getMessagesByArticleId(articleId) { + return apiService.get(`/messages/article/${articleId}`) + } + + /** + * 获取根留言 + * @returns {Promise} + */ + getRootMessages() { + return apiService.get('/messages/root') + } + + /** + * 根据父留言ID获取回复 + * @param {number} parentId - 父留言ID + * @returns {Promise} + */ + getRepliesByParentId(parentId) { + return apiService.get(`/messages/parent/${parentId}`) + } + + /** + * 根据昵称搜索留言 + * @param {string} nickname - 昵称 + * @returns {Promise} + */ + searchMessagesByNickname(nickname) { + return apiService.get(`/messages/search?nickname=${nickname}`) + } + + /** + * 获取文章评论数量 + * @param {number} articleId - 文章ID + * @returns {Promise} + */ + getMessageCountByArticleId(articleId) { + return apiService.get(`/messages/count/${articleId}`) + } + + /** + * 创建留言 + * @param {Object} messageData - 留言数据 + * @returns {Promise} + */ + saveMessage(messageData) { + return apiService.post('/messages', messageData) + } + + /** + * 删除留言 + * @param {number} id - 留言ID + * @returns {Promise} + */ + deleteMessage(id) { + return apiService.delete(`/messages/${id}`) + } +} + +// 导出留言服务实例 +export default new MessageService() \ No newline at end of file diff --git a/src/assets/index.css b/src/styles/MainLayout.css similarity index 92% rename from src/assets/index.css rename to src/styles/MainLayout.css index c9d9ead..33c3e9d 100644 --- a/src/assets/index.css +++ b/src/styles/MainLayout.css @@ -122,6 +122,23 @@ p { align-items: center; } +/* Logo文本样式 - 清疯不颠 */ +.logo-text { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + font-family: 'Microsoft YaHei', 'Ma Shan Zheng', cursive; + font-size: 1rem; + font-weight: bold; + background: linear-gradient(94.75deg, rgb(60, 172, 247) 0%, rgb(131, 101, 253) 43.66%, rgb(255, 141, 112) 64.23%, rgb(247, 201, 102) 83.76%, rgb(172, 143, 100) 100%); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1); +} + /* 导航栏透明状态 */ .elrow-top.transparent { background-color: var(--nav-bg-transparent); @@ -143,8 +160,10 @@ p { } .grid-content.ep-bg-purple-dark { + display: flex; align-items: center; - + height: 60px; + width: 100%; } /* 菜单样式 */ @@ -343,6 +362,7 @@ p { /* 水平居中 */ align-items: center; /* 垂直居中 */ + } .el-menu--vertical:not(.el-menu--collapse):not(.el-menu--popup-container) .el-menu-item, diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..7971516 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,79 @@ +// 项目中使用的类型定义 + +/** + * 文章类型接口 + */ +export interface Article { + id: number + title: string + content: string + author: string + createTime: string + updateTime: string + categoryId: number + categoryName?: string + tags?: string + views?: number + commentCount?: number + articleid?: string + publishedAt?: string + mg?: string +} + +/** + * 留言类型接口 + */ +export interface Message { + id: number + content: string + nickname: string + email: string + articleId?: number + parentId?: number + createdAt: string + replies?: Message[] + time?: string +} + +/** + * 分类类型接口 + */ +export interface Category { + id: number + name: string + description?: string + articleCount?: number +} + +/** + * API响应接口 + */ +export interface ApiResponse { + success: boolean + code: number + message?: string + data?: T + total?: number +} + +/** + * 分页参数接口 + */ +export interface PaginationParams { + page?: number + size?: number + keyword?: string + [key: string]: any +} + +/** + * 用户信息接口 + */ +export interface User { + id?: number + username?: string + email?: string + avatar?: string + role?: string + token?: string +} \ No newline at end of file diff --git a/src/utils/dateUtils.js b/src/utils/dateUtils.js new file mode 100644 index 0000000..da1f02b --- /dev/null +++ b/src/utils/dateUtils.js @@ -0,0 +1,90 @@ +// 日期格式化工具函数 + +/** + * 格式化日期为指定格式 + * @param {string|Date} date - 日期对象或日期字符串 + * @param {string} format - 格式化模板,如 'YYYY-MM-DD HH:mm:ss' + * @returns {string} 格式化后的日期字符串 + */ +export const formatDate = (date, format = 'YYYY-MM-DD HH:mm:ss') => { + if (!date) return '' + + // 如果是字符串,转换为日期对象 + const dateObj = typeof date === 'string' ? new Date(date) : date + + // 检查日期对象是否有效 + if (isNaN(dateObj.getTime())) return '' + + const year = dateObj.getFullYear() + const month = String(dateObj.getMonth() + 1).padStart(2, '0') + const day = String(dateObj.getDate()).padStart(2, '0') + const hours = String(dateObj.getHours()).padStart(2, '0') + const minutes = String(dateObj.getMinutes()).padStart(2, '0') + const seconds = String(dateObj.getSeconds()).padStart(2, '0') + + // 替换模板中的日期部分 + return format + .replace('YYYY', year) + .replace('MM', month) + .replace('DD', day) + .replace('HH', hours) + .replace('mm', minutes) + .replace('ss', seconds) +} + +/** + * 计算时间差,返回相对时间 + * @param {string|Date} date - 日期对象或日期字符串 + * @returns {string} 相对时间字符串 + */ +export const formatRelativeTime = (date) => { + if (!date) return '' + + const dateObj = typeof date === 'string' ? new Date(date) : date + const now = new Date() + const diff = now - dateObj + + // 转换为秒 + const seconds = Math.floor(diff / 1000) + + if (seconds < 60) { + return `${seconds}秒前` + } + + // 转换为分钟 + const minutes = Math.floor(seconds / 60) + if (minutes < 60) { + return `${minutes}分钟前` + } + + // 转换为小时 + const hours = Math.floor(minutes / 60) + if (hours < 24) { + return `${hours}小时前` + } + + // 转换为天 + const days = Math.floor(hours / 24) + if (days < 30) { + return `${days}天前` + } + + // 转换为月 + const months = Math.floor(days / 30) + if (months < 12) { + return `${months}个月前` + } + + // 转换为年 + const years = Math.floor(months / 12) + return `${years}年前` +} + +/** + * 获取当前日期 + * @param {string} format - 格式化模板 + * @returns {string} 当前日期字符串 + */ +export const getCurrentDate = (format = 'YYYY-MM-DD HH:mm:ss') => { + return formatDate(new Date(), format) +} \ No newline at end of file diff --git a/src/utils/stringUtils.js b/src/utils/stringUtils.js new file mode 100644 index 0000000..c7c673e --- /dev/null +++ b/src/utils/stringUtils.js @@ -0,0 +1,75 @@ +// 字符串处理工具函数 + +/** + * 截断字符串并添加省略号 + * @param {string} str - 原始字符串 + * @param {number} length - 保留的长度 + * @returns {string} 截断后的字符串 + */ +export const truncateString = (str, length) => { + if (!str || typeof str !== 'string' || str.length <= length) { + return str + } + return str.substring(0, length) + '...' +} + +/** + * 移除HTML标签 + * @param {string} html - 包含HTML标签的字符串 + * @returns {string} 纯文本字符串 + */ +export const stripHtmlTags = (html) => { + if (!html || typeof html !== 'string') { + return html + } + return html.replace(/<[^>]*>/g, '') +} + +/** + * 格式化内容预览 + * @param {string} content - 原始内容 + * @param {number} maxLength - 最大长度 + * @returns {string} 格式化后的预览内容 + */ +export const formatContentPreview = (content, maxLength = 200) => { + if (!content) return '' + // 先移除HTML标签 + const plainText = stripHtmlTags(content) + // 再截断字符串 + return truncateString(plainText, maxLength) +} + +/** + * 转义HTML特殊字符 + * @param {string} str - 原始字符串 + * @returns {string} 转义后的字符串 + */ +export const escapeHtml = (str) => { + if (!str || typeof str !== 'string') { + return str + } + const map = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + } + return str.replace(/[&<>"']/g, (m) => map[m]) +} + +/** + * 解析URL查询参数 + * @param {string} url - URL字符串 + * @returns {Object} 查询参数对象 + */ +export const parseQueryParams = (url) => { + const queryString = url.split('?')[1] + if (!queryString) return {} + + return queryString.split('&').reduce((params, param) => { + const [key, value] = param.split('=') + params[decodeURIComponent(key)] = decodeURIComponent(value || '') + return params + }, {}) +} \ No newline at end of file diff --git a/src/views/aboutme.vue b/src/views/aboutme.vue index e301f90..e6cee67 100644 --- a/src/views/aboutme.vue +++ b/src/views/aboutme.vue @@ -1,11 +1,213 @@ - - \ No newline at end of file + + \ No newline at end of file diff --git a/src/views/aericle.vue b/src/views/aericle.vue index 6298aba..bd51bce 100644 --- a/src/views/aericle.vue +++ b/src/views/aericle.vue @@ -1,39 +1,94 @@ - - \ No newline at end of file diff --git a/src/views/articlecontents.vue b/src/views/articlecontents.vue index b44aa85..c48b4d6 100644 --- a/src/views/articlecontents.vue +++ b/src/views/articlecontents.vue @@ -1,31 +1,484 @@ - - \ No newline at end of file + + diff --git a/src/views/home.vue b/src/views/home.vue index 37b0db0..b3e753a 100644 --- a/src/views/home.vue +++ b/src/views/home.vue @@ -1,61 +1,145 @@ - + diff --git a/src/views/leftmodlue.vue b/src/views/leftmodlue.vue deleted file mode 100644 index f2db8bb..0000000 --- a/src/views/leftmodlue.vue +++ /dev/null @@ -1,212 +0,0 @@ - - - \ No newline at end of file diff --git a/src/views/messageboard.vue b/src/views/messageboard.vue index d2c6a4e..e43ab37 100644 --- a/src/views/messageboard.vue +++ b/src/views/messageboard.vue @@ -1,105 +1,284 @@