feat: 添加分类树接口和分页组件,优化留言板功能
refactor: 重构文章服务和留言服务API调用方式 fix: 修复token验证逻辑和全局状态管理问题 style: 更新页脚样式和关于页面内容 docs: 添加类型定义和接口注释
This commit is contained in:
87
src/components/CustomPagination.vue
Normal file
87
src/components/CustomPagination.vue
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<template>
|
||||||
|
<div class="custom-pagination">
|
||||||
|
<el-pagination
|
||||||
|
size="medium"
|
||||||
|
background
|
||||||
|
:layout="pageLayout"
|
||||||
|
v-model:current-page="localPageNum"
|
||||||
|
hide-on-single-page="true"
|
||||||
|
@current-change="handlePageChange"
|
||||||
|
:page-size="pageSize"
|
||||||
|
:page-count="totalPages"
|
||||||
|
class="mt-4"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, watch } from 'vue';
|
||||||
|
|
||||||
|
// 定义组件的属性
|
||||||
|
const props = defineProps<{
|
||||||
|
pageNum: number;
|
||||||
|
pageSize: number;
|
||||||
|
totalPages: number;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
// 定义组件的事件
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:pageNum', value: number): void;
|
||||||
|
(e: 'pageChange', value: number): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
// 响应式状态
|
||||||
|
const localPageNum = ref(props.pageNum);
|
||||||
|
const localTotalPages = ref(props.totalPages);
|
||||||
|
|
||||||
|
// 计算属性:根据当前页码动态生成分页布局
|
||||||
|
const pageLayout = computed(() => {
|
||||||
|
if (localPageNum.value === 1) {
|
||||||
|
// 第一页只显示页码和下一页按钮
|
||||||
|
return 'pager, next';
|
||||||
|
} else if (localPageNum.value === localTotalPages.value) {
|
||||||
|
// 最后一页只显示上一页按钮和页码
|
||||||
|
return 'prev, pager';
|
||||||
|
} else {
|
||||||
|
// 中间页显示完整的上一页、页码、下一页
|
||||||
|
return 'prev, pager, next';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听props变化,更新本地状态
|
||||||
|
watch(
|
||||||
|
() => props.pageNum,
|
||||||
|
(newVal) => {
|
||||||
|
localPageNum.value = newVal;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.totalPages,
|
||||||
|
(newVal) => {
|
||||||
|
localTotalPages.value = newVal;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 处理页码变化
|
||||||
|
const handlePageChange = (newPage: number) => {
|
||||||
|
// 更新本地页码
|
||||||
|
localPageNum.value = newPage;
|
||||||
|
// 触发update:pageNum事件,实现双向绑定
|
||||||
|
emit('update:pageNum', newPage);
|
||||||
|
// 触发pageChange事件,通知父组件页码变化
|
||||||
|
emit('pageChange', newPage);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.custom-pagination {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-4 {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -110,7 +110,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
<!-- 管理员 -->
|
<!-- 管理员 -->
|
||||||
<Establish class="establish-container" v-if="Login" />
|
<Establish class="establish-container" v-if="Login" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
@@ -243,7 +243,7 @@ const handleCategoryClick = (category: any) => {
|
|||||||
id: category.categoryid,
|
id: category.categoryid,
|
||||||
name: category.categoryname
|
name: category.categoryname
|
||||||
})
|
})
|
||||||
console.log(category)
|
// console.log(category)
|
||||||
router.push('/home/aericlecategory',)
|
router.push('/home/aericlecategory',)
|
||||||
closeCategoryModal()
|
closeCategoryModal()
|
||||||
}
|
}
|
||||||
@@ -317,24 +317,6 @@ const handleSelect = (key: string) => {
|
|||||||
router.push({ path: '/' + key });
|
router.push({ path: '/' + key });
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置当前激活的菜单项并存储路径信息
|
|
||||||
* @param {string} path - 当前路由路径
|
|
||||||
*/
|
|
||||||
const setActiveIndex = (path: string) => {
|
|
||||||
// 存储当前路径到全局状态
|
|
||||||
globalStore.setValue('localpath', {
|
|
||||||
name: path
|
|
||||||
});
|
|
||||||
|
|
||||||
// 特殊处理消息页面,清除文章信息
|
|
||||||
if (path === 'message') {
|
|
||||||
globalStore.removeValue('articleInfo');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新激活菜单项
|
|
||||||
activeIndex.value = path;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据路由路径设置页面状态
|
* 根据路由路径设置页面状态
|
||||||
@@ -532,11 +514,13 @@ const updateNavbarStyle = (scrollY: number) => {
|
|||||||
const handleRouteChange = () => {
|
const handleRouteChange = () => {
|
||||||
// 重新解析路由路径
|
// 重新解析路由路径
|
||||||
rpsliturl = route.path.split('/');
|
rpsliturl = route.path.split('/');
|
||||||
|
// 移除全局状态值
|
||||||
|
removeGlobalStoreValue();
|
||||||
// 更新页面相关状态
|
// 更新页面相关状态
|
||||||
updatePageState();
|
updatePageState();
|
||||||
setActiveIndex(rpsliturl[1]);
|
// setActiveIndex(rpsliturl[1]);
|
||||||
updateArticleTitle();
|
updateArticleTitle();
|
||||||
|
activeIndex.value = rpsliturl[1];
|
||||||
// 页面跳转后回到顶部
|
// 页面跳转后回到顶部
|
||||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
|
||||||
@@ -553,7 +537,7 @@ const handleRouteChange = () => {
|
|||||||
} else {
|
} else {
|
||||||
iscontentvisible.value = true;
|
iscontentvisible.value = true;
|
||||||
startTypewriter(fullHeroText);
|
startTypewriter(fullHeroText);
|
||||||
heroMarginBottom.value = `${5}%`;
|
heroMarginBottom.value = `${2.5}%`;
|
||||||
heroTransform.value = ``;
|
heroTransform.value = ``;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -581,7 +565,26 @@ const initializePage = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
const removeGlobalStoreValue = () => {
|
||||||
|
// 根据所在页面判断是否需要移除全局状态值
|
||||||
|
if (rpsliturl[1] === localhome && rpsliturl[2] == undefined) {
|
||||||
|
// 首页移除全局状态值
|
||||||
|
// globalStore.removeValue(value);
|
||||||
|
}
|
||||||
|
// 不在articlesave移除文章全局状态值
|
||||||
|
if (rpsliturl[1] !== 'articlesave') {
|
||||||
|
globalStore.removeValue('updatearticle');
|
||||||
|
}
|
||||||
|
// 不在首页移除
|
||||||
|
if (rpsliturl[1] !== localhome) {
|
||||||
|
globalStore.removeValue('articlestatus');
|
||||||
|
globalStore.removeValue('attribute');
|
||||||
|
}
|
||||||
|
// 不在article或者不在articlesave移除文章全局状态值
|
||||||
|
if (rpsliturl[1] !== 'article' && rpsliturl[1] !== 'articlesave') {
|
||||||
|
globalStore.removeValue('articleInfo');
|
||||||
|
}
|
||||||
|
}
|
||||||
// ========== 生命周期钩子 ==========
|
// ========== 生命周期钩子 ==========
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -621,7 +624,7 @@ onUnmounted(() => {
|
|||||||
*/
|
*/
|
||||||
watch(
|
watch(
|
||||||
() => route.path,
|
() => route.path,
|
||||||
handleRouteChange
|
handleRouteChange,
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ const pageButtons = {
|
|||||||
|
|
||||||
// 文章保存页面按钮
|
// 文章保存页面按钮
|
||||||
articlesave: [
|
articlesave: [
|
||||||
{ id: 'view-articles', label: '查看文章列表', icon: 'icon-new-tag' },
|
{ id: 'view-articles', label: '文章', icon: 'icon-new-tag' },
|
||||||
...baseButtons
|
...baseButtons
|
||||||
],
|
],
|
||||||
|
|
||||||
@@ -164,7 +164,8 @@ const pageButtons = {
|
|||||||
const isbuttonsave = () => {
|
const isbuttonsave = () => {
|
||||||
try {
|
try {
|
||||||
// 获取当前页面路径名称
|
// 获取当前页面路径名称
|
||||||
const currentPath = globalStore.getValue('localpath')?.name || 'default';
|
// const currentPath = globalStore.getValue('localpath')?.name || 'default';
|
||||||
|
const currentPath = router.currentRoute.value.path.split('/')[1];
|
||||||
// 返回对应页面的按钮配置,如果没有则返回默认配置
|
// 返回对应页面的按钮配置,如果没有则返回默认配置
|
||||||
return pageButtons[currentPath] || pageButtons.default;
|
return pageButtons[currentPath] || pageButtons.default;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -9,12 +9,53 @@ const api = axios.create({
|
|||||||
withCredentials: true // 允许跨域请求携带凭证(如cookies)
|
withCredentials: true // 允许跨域请求携带凭证(如cookies)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 解析JWT token,获取过期时间
|
||||||
|
const parseJwt = (token) => {
|
||||||
|
try {
|
||||||
|
// 移除Bearer前缀(如果有)
|
||||||
|
const pureToken = token.replace('Bearer ', '')
|
||||||
|
// 解析payload部分
|
||||||
|
const base64Url = pureToken.split('.')[1]
|
||||||
|
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
|
||||||
|
const jsonPayload = decodeURIComponent(
|
||||||
|
window
|
||||||
|
.atob(base64)
|
||||||
|
.split('')
|
||||||
|
.map((c) => `%${`00${c.charCodeAt(0).toString(16)}`.slice(-2)}`)
|
||||||
|
.join('')
|
||||||
|
)
|
||||||
|
return JSON.parse(jsonPayload)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('解析JWT token失败:', e)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证token是否过期
|
||||||
|
const isTokenExpired = (token) => {
|
||||||
|
const decodedToken = parseJwt(token)
|
||||||
|
if (!decodedToken || !decodedToken.exp) {
|
||||||
|
return true // 如果解析失败或没有过期时间,认为token无效
|
||||||
|
}
|
||||||
|
// 比较过期时间和当前时间(转换为秒)
|
||||||
|
const currentTime = Math.floor(Date.now() / 1000)
|
||||||
|
return decodedToken.exp < currentTime
|
||||||
|
}
|
||||||
|
|
||||||
// 请求拦截器
|
// 请求拦截器
|
||||||
api.interceptors.request.use(
|
api.interceptors.request.use(
|
||||||
config => {
|
config => {
|
||||||
// 从localStorage获取token
|
// 从localStorage获取token
|
||||||
let token = localStorage.getItem('token')
|
let token = localStorage.getItem('token')
|
||||||
if (token) {
|
if (token) {
|
||||||
|
// 验证token是否过期
|
||||||
|
if (isTokenExpired(token)) {
|
||||||
|
// token过期,移除token并跳转到登录页
|
||||||
|
localStorage.removeItem('token')
|
||||||
|
ElMessage.error('登录已过期,请重新登录')
|
||||||
|
// window.location.href = '/login'
|
||||||
|
return Promise.reject(new Error('token已过期'))
|
||||||
|
}
|
||||||
// 确保不重复添加Bearer前缀
|
// 确保不重复添加Bearer前缀
|
||||||
if (!token.startsWith('Bearer ')) {
|
if (!token.startsWith('Bearer ')) {
|
||||||
config.headers['Authorization'] = `Bearer ${token}`
|
config.headers['Authorization'] = `Bearer ${token}`
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ import api from './apiService'
|
|||||||
class ArticleService {
|
class ArticleService {
|
||||||
/**
|
/**
|
||||||
* 分页查询文章列表
|
* 分页查询文章列表
|
||||||
* @param {import('../types').PaginationParams} params - 分页查询参数
|
* @param {import('../types').ArticlePagination} params - 分页查询参数
|
||||||
* @param status 文章状态(0:未发表 1:已发表 2:已删除)
|
* @param status 文章状态(0:未发表 1:已发表 2:已删除)
|
||||||
* @param page 页码,从0开始(可选,默认为0)
|
* @param page 页码,从0开始(可选,默认为0)
|
||||||
* @param size 每页大小(可选,默认为10,最大为100)
|
* @param size 每页大小(可选,默认为10,最大为100)
|
||||||
* @returns {Promise<import('../types').ApiResponse<import('../types').Article[]>>}
|
* @returns {Promise<import('../types').ApiResponse<import('../types').Article[]>>}
|
||||||
*/
|
*/
|
||||||
getPagedArticles(params = {}) {
|
getPagedArticles(params = {}) {
|
||||||
return api.get(`/articles/status/page?title=${params.title || ''}&categoryid=${params.categoryid || 0}&attributeid=${params.attributeid || 0}&status=${params.status || 1}&page=${params.page || 0}&size=${params.size || 10}`)
|
return api.get(`/articles/status/page?title=${params.title || ''}&categoryid=${params.categoryid || ''}&attributeid=${params.attributeid || ''}&status=${params.status !== undefined ? params.status : 1}&page=${params.page || 0}&size=${params.size || 10}`)
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 获取已发布文章列表
|
* 获取已发布文章列表
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class CategoryAttributeService {
|
|||||||
* @returns {Promise<import('../types').ApiResponse<import('../types').CategoryAttribute[]>>}
|
* @returns {Promise<import('../types').ApiResponse<import('../types').CategoryAttribute[]>>}
|
||||||
*/
|
*/
|
||||||
getAllAttributes() {
|
getAllAttributes() {
|
||||||
return api.get('/category-attributes')
|
return api.get('/categoryattributes')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,7 +19,7 @@ class CategoryAttributeService {
|
|||||||
* @returns {Promise<import('../types').ApiResponse<import('../types').CategoryAttribute>>}
|
* @returns {Promise<import('../types').ApiResponse<import('../types').CategoryAttribute>>}
|
||||||
*/
|
*/
|
||||||
getAttributeById(attributeid) {
|
getAttributeById(attributeid) {
|
||||||
return api.get(`/category-attributes/${attributeid}`)
|
return api.get(`/categoryattributes/${attributeid}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,7 +28,7 @@ class CategoryAttributeService {
|
|||||||
* @returns {Promise<import('../types').ApiResponse<import('../types').CategoryAttribute[]>>}
|
* @returns {Promise<import('../types').ApiResponse<import('../types').CategoryAttribute[]>>}
|
||||||
*/
|
*/
|
||||||
getAttributesByCategory(categoryid) {
|
getAttributesByCategory(categoryid) {
|
||||||
return api.get(`/category-attributes/category/${categoryid}`)
|
return api.get(`/categoryattributes/category/${categoryid}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,7 +37,7 @@ class CategoryAttributeService {
|
|||||||
* @returns {Promise<import('../types').ApiResponse<import('../types').CategoryAttribute>>}
|
* @returns {Promise<import('../types').ApiResponse<import('../types').CategoryAttribute>>}
|
||||||
*/
|
*/
|
||||||
createAttribute(attributeData) {
|
createAttribute(attributeData) {
|
||||||
return api.post('/category-attributes', attributeData)
|
return api.post('/categoryattributes', attributeData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,13 @@ class CategoryService {
|
|||||||
getAllCategories() {
|
getAllCategories() {
|
||||||
return api.get('/categories')
|
return api.get('/categories')
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 获取分类树
|
||||||
|
* @returns {Promise<import('../types').ApiResponse<import('../types').CategoryTree[]>>}
|
||||||
|
*/
|
||||||
|
getCategoryTree() {
|
||||||
|
return api.get('/categories/tree')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建并导出服务实例
|
// 创建并导出服务实例
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class MessageService {
|
|||||||
* @returns {Promise<import('../types').ApiResponse<number>>}
|
* @returns {Promise<import('../types').ApiResponse<number>>}
|
||||||
*/
|
*/
|
||||||
getMessageCountByArticleId(articleid) {
|
getMessageCountByArticleId(articleid) {
|
||||||
return apiService.get(`/messages/count?articleid=${articleid}`)
|
return apiService.get(`/messages/count/${articleid}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,9 +33,6 @@ class MessageService {
|
|||||||
* @param {number} articleid - 文章ID
|
* @param {number} articleid - 文章ID
|
||||||
* @returns {Promise<import('../types').ApiResponse<import('../types').Message[]>>}
|
* @returns {Promise<import('../types').ApiResponse<import('../types').Message[]>>}
|
||||||
*/
|
*/
|
||||||
getMessagesByArticleId(articleid) {
|
|
||||||
return apiService.get(`/messages/article/${articleid}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有留言
|
* 获取所有留言
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ class NonsenseService {
|
|||||||
return apiService.get('/nonsense')
|
return apiService.get('/nonsense')
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 根据状态获取疯言疯语内容
|
* 根据分页信息获取疯言疯语内容
|
||||||
* @param {number} status - 状态值(1:已发表, 0:草稿)
|
* @param {import('../types').NosensePageDto} page - 分页信息对象
|
||||||
* @returns {Promise<import('../types').ApiResponse<import('../types').Nonsense[]>>}
|
* @returns {Promise<import('../types').ApiResponse<import('../types').Nonsense[]>>}
|
||||||
*/
|
*/
|
||||||
getNonsenseByStatus(status){
|
getNonsenseByStatus(page){
|
||||||
return apiService.get(`/nonsense/status/${status}`)
|
return apiService.get(`/nonsense/page?status=${page.status}&pageNum=${page.pageNum}&pageSize=${page.pageSize}`)
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 保存疯言疯语内容
|
* 保存疯言疯语内容
|
||||||
|
|||||||
@@ -32,9 +32,18 @@ export interface ArticleDto {
|
|||||||
viewCount?: number
|
viewCount?: number
|
||||||
likes?: number
|
likes?: number
|
||||||
markdownscontent: string
|
markdownscontent: string
|
||||||
|
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 文章分页接口
|
||||||
|
*/
|
||||||
|
export interface ArticlePagination {
|
||||||
|
status?: number | undefined
|
||||||
|
title?: string | undefined
|
||||||
|
attributeid?: number | undefined
|
||||||
|
categoryid?: number | undefined
|
||||||
|
pagenum?: number | undefined
|
||||||
|
pagesize?: number | undefined
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 留言类型接口
|
* 留言类型接口
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
<!-- 版权信息 -->
|
<!-- 版权信息 -->
|
||||||
<p class="footer-copyright">
|
<p class="footer-copyright">
|
||||||
网站所有权利保留 © {{ new Date().getFullYear() }} 清疯不颠
|
网站所有权利保留 © {{ new Date().getFullYear() }} <a href="https://www.qf1121.top" target="_blank">清疯不颠</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- 运行时间 -->
|
<!-- 运行时间 -->
|
||||||
@@ -74,6 +74,8 @@ onUnmounted(() => {
|
|||||||
margin-top: 3rem;
|
margin-top: 3rem;
|
||||||
padding: 2rem 0;
|
padding: 2rem 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 页脚内容 */
|
/* 页脚内容 */
|
||||||
|
|||||||
@@ -92,7 +92,7 @@
|
|||||||
<a href="https://gradle.org/" target="_blank" class="skill-tag-link"><el-tag
|
<a href="https://gradle.org/" target="_blank" class="skill-tag-link"><el-tag
|
||||||
type="success">Gradle</el-tag></a>
|
type="success">Gradle</el-tag></a>
|
||||||
<a href="https://www.oracle.com/java/technologies/java17.html" target="_blank"
|
<a href="https://www.oracle.com/java/technologies/java17.html" target="_blank"
|
||||||
class="skill-tag-link"><el-tag type="success">Java 17+</el-tag></a>
|
class="skill-tag-link"><el-tag type="success">Java 8</el-tag></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -124,7 +124,6 @@ const goToMessageBoard = () => {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
/* 主容器样式 */
|
/* 主容器样式 */
|
||||||
.about-page-container {
|
.about-page-container {
|
||||||
width: 80%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 内容包装器样式 */
|
/* 内容包装器样式 */
|
||||||
|
|||||||
@@ -19,11 +19,11 @@
|
|||||||
<h1 class="article-main-title">{{ article.title }}</h1>
|
<h1 class="article-main-title">{{ article.title }}</h1>
|
||||||
|
|
||||||
<div class="article-meta-info">
|
<div class="article-meta-info">
|
||||||
<span class="article-publish-date">{{ formatRelativeTime(article.createdAt) }}</span>
|
<span class="article-publish-date">{{ formatRelativeTime(article.createtime) }}</span>
|
||||||
<span v-if="article.categoryName" class="article-category-badge">{{ article.categoryName }} </span>
|
<span v-if="article.attributename" class="article-category-badge">{{ article.attributename }} </span>
|
||||||
<span v-if="article.viewCount" class="article-views-count">{{ article.viewCount }} 阅读</span>
|
<span v-if="article.viewcount" class="article-views-count">{{ article.viewcount }} 阅读</span>
|
||||||
<span v-if="article.likes > 0" class="article-likes-count">{{ article.likes }} 点赞</span>
|
<span v-if="article.likes > 0" class="article-likes-count">{{ article.likes }} 点赞</span>
|
||||||
<span v-if="article.commentCount > 0" class="article-comments-count">{{ article.commentCount }} 评论</span>
|
<span v-if="article.commentcount > 0" class="article-comments-count">{{ article.commentcount }} 评论</span>
|
||||||
<div class="article-status-badge-container" v-if="globalStore.Login">
|
<div class="article-status-badge-container" v-if="globalStore.Login">
|
||||||
<span v-if="article.status === 0" class="article-status-badge badge badge-warning">草稿</span>
|
<span v-if="article.status === 0" class="article-status-badge badge badge-warning">草稿</span>
|
||||||
<span v-if="article.status === 1" class="article-status-badge badge badge-success">已发表</span>
|
<span v-if="article.status === 1" class="article-status-badge badge badge-success">已发表</span>
|
||||||
@@ -100,7 +100,7 @@ import { ElMessage } from 'element-plus'
|
|||||||
/**
|
/**
|
||||||
* 导入类型和工具函数
|
* 导入类型和工具函数
|
||||||
*/
|
*/
|
||||||
import type { Article } from '@/types'
|
// import type { Article } from '@/types'
|
||||||
import { formatRelativeTime } from '@/utils/dateUtils'
|
import { formatRelativeTime } from '@/utils/dateUtils'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -126,10 +126,10 @@ const globalStore = useGlobalStore()
|
|||||||
/**
|
/**
|
||||||
* 响应式状态定义
|
* 响应式状态定义
|
||||||
*/
|
*/
|
||||||
const article = ref<Article | null>(null) // 文章详情数据,初始为null避免类型不匹配
|
const article = ref<any | null>(null) // 文章详情数据,初始为null避免类型不匹配
|
||||||
const loading = ref(false) // 加载状态
|
const loading = ref(false) // 加载状态
|
||||||
const error = ref('') // 错误信息
|
const error = ref('') // 错误信息
|
||||||
const relatedArticles = ref<Article[]>([]) // 相关文章列表
|
const relatedArticles = ref<any[]>([]) // 相关文章列表
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// 文章数据处理模块
|
// 文章数据处理模块
|
||||||
@@ -147,10 +147,11 @@ const getArticleId = (): number | null => {
|
|||||||
* 获取文章详情数据
|
* 获取文章详情数据
|
||||||
* @returns {Promise<Article | null>} 文章数据或null
|
* @returns {Promise<Article | null>} 文章数据或null
|
||||||
*/
|
*/
|
||||||
const fetchArticleData = async (): Promise<Article | null> => {
|
const fetchArticleData = async (): Promise<any | null> => {
|
||||||
try {
|
try {
|
||||||
// 从全局状态获取文章信息
|
// 从全局状态获取文章信息
|
||||||
const response = await globalStore.getValue('articleInfo')
|
const response = await globalStore.getValue('articleInfo')
|
||||||
|
console.log('获取文章数据:', response)
|
||||||
return response || null
|
return response || null
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('获取文章数据失败:', err)
|
console.error('获取文章数据失败:', err)
|
||||||
@@ -162,11 +163,11 @@ const fetchArticleData = async (): Promise<Article | null> => {
|
|||||||
* 增加文章浏览量
|
* 增加文章浏览量
|
||||||
* @param {Article} articleData 文章数据
|
* @param {Article} articleData 文章数据
|
||||||
*/
|
*/
|
||||||
const incrementViewCount = (articleData: Article): void => {
|
const incrementViewCount = (articleData: any): void => {
|
||||||
if (articleData.viewCount) {
|
if (articleData.viewcount) {
|
||||||
articleData.viewCount++
|
articleData.viewcount++
|
||||||
} else {
|
} else {
|
||||||
articleData.viewCount = 1
|
articleData.viewcount = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,16 +44,15 @@
|
|||||||
<!-- 编辑区域 -->
|
<!-- 编辑区域 -->
|
||||||
<MdEditor v-model="Articleform.markdownscontent" class="markdown-editor" @on-save="handleSave" noImgZoomIn
|
<MdEditor v-model="Articleform.markdownscontent" class="markdown-editor" @on-save="handleSave" noImgZoomIn
|
||||||
noKatex />
|
noKatex />
|
||||||
<!-- 返回列表 -->
|
|
||||||
<el-button type="primary" @click="handleReturn">返回列表</el-button>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 返回列表 -->
|
||||||
|
<el-button class="return-btn" type="primary" @click="handleReturn">返回列表</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, ref } from 'vue';
|
import { onMounted, reactive, ref } from 'vue';
|
||||||
import { MdEditor } from 'md-editor-v3';
|
import { MdEditor } from 'md-editor-v3';
|
||||||
import 'md-editor-v3/lib/style.css';
|
import 'md-editor-v3/lib/style.css';
|
||||||
import { categoryService, categoryAttributeService, articleService } from '@/services';
|
import { categoryService, categoryAttributeService, articleService } from '@/services';
|
||||||
@@ -61,9 +60,6 @@ import type { Article } from '@/types/index.ts';
|
|||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
import { useGlobalStore } from '@/store/globalStore'
|
import { useGlobalStore } from '@/store/globalStore'
|
||||||
const globalStore = useGlobalStore()
|
const globalStore = useGlobalStore()
|
||||||
// 测试
|
|
||||||
// const id = 'preview-only';
|
|
||||||
// const scrollElement = document.documentElement;
|
|
||||||
|
|
||||||
// 路由
|
// 路由
|
||||||
import router from '@/router/Router';
|
import router from '@/router/Router';
|
||||||
@@ -95,7 +91,6 @@ const statusoptions = ref([
|
|||||||
value: '2'
|
value: '2'
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
const categories = ref([]);
|
|
||||||
// 编辑文章
|
// 编辑文章
|
||||||
const editArticle = globalStore.getValue('updatearticle')
|
const editArticle = globalStore.getValue('updatearticle')
|
||||||
|
|
||||||
@@ -109,53 +104,21 @@ if (editArticle) {
|
|||||||
// 初始化加载分类和属性,构建级联选择器的options
|
// 初始化加载分类和属性,构建级联选择器的options
|
||||||
const loadCategories = async () => {
|
const loadCategories = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await categoryService.getAllCategories();
|
const categories = ref([]);
|
||||||
|
const response = await categoryService.getCategoryTree();
|
||||||
if (response.code === 200) {
|
if (response.code === 200) {
|
||||||
categories.value = response.data;
|
categories.value = response.data;
|
||||||
|
// 构建级联选择器的options
|
||||||
// 为每个分类加载对应的属性,并构建options格式
|
categorieoptions.value = categories.value.map(category => ({
|
||||||
const optionsData = await Promise.all(
|
label: category.name,
|
||||||
categories.value.map(async (category) => {
|
value: category.id,
|
||||||
try {
|
children: category.children.map(child => ({
|
||||||
const attrResponse = await categoryAttributeService.getAttributesByCategory(category.typeid);
|
label: child.attributename,
|
||||||
const children = attrResponse.code === 200 && attrResponse.data ?
|
value: child.attributeid
|
||||||
attrResponse.data.map(attr => ({
|
}))
|
||||||
label: attr.attributename,
|
}));
|
||||||
value: attr.attributeid.toString()
|
} else {
|
||||||
})) : [];
|
ElMessage.error('加载分类失败: ' + response.message);
|
||||||
|
|
||||||
return {
|
|
||||||
label: category.typename,
|
|
||||||
value: category.typeid.toString(),
|
|
||||||
children
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`加载分类 ${category.typename} 的属性失败:`, error);
|
|
||||||
return {
|
|
||||||
label: category.typename,
|
|
||||||
value: category.typeid.toString(),
|
|
||||||
children: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
categorieoptions.value = 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) {
|
} catch (error) {
|
||||||
console.error('加载分类失败:', error);
|
console.error('加载分类失败:', error);
|
||||||
@@ -172,8 +135,6 @@ const handleCascaderChange = (values) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 组件挂载时加载分类和属性
|
|
||||||
loadCategories();
|
|
||||||
|
|
||||||
const handleSave = (markdown) => {
|
const handleSave = (markdown) => {
|
||||||
Articleform.value.markdownscontent = markdown;
|
Articleform.value.markdownscontent = markdown;
|
||||||
@@ -198,7 +159,6 @@ const handleSave = (markdown) => {
|
|||||||
|
|
||||||
// console.log('发送文章数据:', articleData);
|
// console.log('发送文章数据:', articleData);
|
||||||
// console.log('当前认证token是否存在:', !!localStorage.getItem('token'));
|
// console.log('当前认证token是否存在:', !!localStorage.getItem('token'));
|
||||||
|
|
||||||
// 根据articleid决定调用创建还是更新接口
|
// 根据articleid决定调用创建还是更新接口
|
||||||
const savePromise = Articleform.value.articleid === 0
|
const savePromise = Articleform.value.articleid === 0
|
||||||
? articleService.createArticle(articleData)
|
? articleService.createArticle(articleData)
|
||||||
@@ -267,6 +227,10 @@ const handleReturn = () => {
|
|||||||
router.push('/home');
|
router.push('/home');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// 组件挂载时加载分类和属性
|
||||||
|
onMounted(() => {
|
||||||
|
loadCategories();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
@@ -370,6 +334,9 @@ const handleReturn = () => {
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.return-btn {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
/* 响应式设计 - 平板 */
|
/* 响应式设计 - 平板 */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.article-content-wrapper {
|
.article-content-wrapper {
|
||||||
@@ -464,7 +431,6 @@ const handleReturn = () => {
|
|||||||
color: #4a9eff;
|
color: #4a9eff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 级联选择器ul元素自适应高度 */
|
/* 级联选择器ul元素自适应高度 */
|
||||||
:deep(.el-cascader-menu__list) {
|
:deep(.el-cascader-menu__list) {
|
||||||
height: auto !important;
|
height: auto !important;
|
||||||
|
|||||||
@@ -15,11 +15,11 @@
|
|||||||
<div v-if="article.marked" class="article-special-tag">标记文章</div>
|
<div v-if="article.marked" class="article-special-tag">标记文章</div>
|
||||||
<p class="article-content-preview">{{ formatContentPreview(article.content, 150) }}</p>
|
<p class="article-content-preview">{{ formatContentPreview(article.content, 150) }}</p>
|
||||||
<div class="article-meta-info">
|
<div class="article-meta-info">
|
||||||
<span class="article-publish-date">{{ formatRelativeTime(article.createdAt || article.createTime) }}</span>
|
<span class="article-publish-date">{{ formatRelativeTime(article.createtime || article.createtime) }}</span>
|
||||||
<span v-if="article.categoryName" class="article-category-badge">{{ article.categoryName }} </span>
|
<span v-if="article.attributename" class="article-category-badge">{{ article.attributename }} </span>
|
||||||
<span v-if="article.viewCount" class="article-views-count">{{ article.viewCount }} 阅读</span>
|
<span v-if="article.viewcount" class="article-views-count">{{ article.viewcount }} 阅读</span>
|
||||||
<span v-if="article.likes > 0" class="article-likes-count">{{ article.likes }} 点赞</span>
|
<span v-if="article.likes > 0" class="article-likes-count">{{ article.likes }} 点赞</span>
|
||||||
<span v-if="article.commentCount > 0" class="article-comments-count">{{ article.commentCount }} 评论</span>
|
<span v-if="article.commentcount > 0" class="article-comments-count">{{ article.commentcount }} 评论</span>
|
||||||
<div class="article-status-badge-container" v-if="globalStore.Login">
|
<div class="article-status-badge-container" v-if="globalStore.Login">
|
||||||
<span v-if="article.status === 0" class="article-status-badge badge badge-warning">草稿</span>
|
<span v-if="article.status === 0" class="article-status-badge badge badge-warning">草稿</span>
|
||||||
<span v-if="article.status === 1" class="article-status-badge badge badge-success">已发表</span>
|
<span v-if="article.status === 1" class="article-status-badge badge badge-success">已发表</span>
|
||||||
@@ -28,7 +28,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 分页区域 -->
|
<!-- 分页区域 -->
|
||||||
<el-pagination size="medium" background :layout="pageLayout" v-model:current-page="pageNum" hide-on-single-page="true" @current-change="changePage" :page-size="pageSize" :page-count="totalPages" class="mt-4" />
|
<CustomPagination
|
||||||
|
v-model:pageNum="pageNum"
|
||||||
|
:pageSize="pageSize"
|
||||||
|
:totalPages="totalPages"
|
||||||
|
@pageChange="changePage"
|
||||||
|
/>
|
||||||
</transition-group>
|
</transition-group>
|
||||||
<!-- 空状态 -->
|
<!-- 空状态 -->
|
||||||
<div v-if="!loading && articleList.length === 0" class="empty-state-container">
|
<div v-if="!loading && articleList.length === 0" class="empty-state-container">
|
||||||
@@ -44,8 +49,8 @@ import { formatRelativeTime } from '@/utils/dateUtils'
|
|||||||
import { formatContentPreview } from '@/utils/stringUtils'
|
import { formatContentPreview } from '@/utils/stringUtils'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { articleService, messageService, categoryAttributeService } from '@/services'
|
import { articleService, messageService, categoryAttributeService } from '@/services'
|
||||||
import PaginationComponent from '@/views/page.vue'
|
|
||||||
import { useGlobalStore } from '@/store/globalStore'
|
import { useGlobalStore } from '@/store/globalStore'
|
||||||
|
import CustomPagination from '@/components/CustomPagination.vue'
|
||||||
|
|
||||||
// ========== 组件初始化 ==========
|
// ========== 组件初始化 ==========
|
||||||
|
|
||||||
@@ -59,7 +64,6 @@ const route = useRoute()
|
|||||||
const pageNum = ref(1) // 当前页码
|
const pageNum = ref(1) // 当前页码
|
||||||
const pageSize = ref(10) // 每页数量
|
const pageSize = ref(10) // 每页数量
|
||||||
const totalPages = ref(0) // 总页数
|
const totalPages = ref(0) // 总页数
|
||||||
const pageLayout = ref('pager, next')// 分页布局
|
|
||||||
|
|
||||||
// 响应式状态
|
// 响应式状态
|
||||||
const articleList = ref([])
|
const articleList = ref([])
|
||||||
@@ -84,7 +88,6 @@ const getArticlesByRoute = async () => {
|
|||||||
// 检查URL参数,确定获取文章的方式
|
// 检查URL参数,确定获取文章的方式
|
||||||
const pathSegment = route.path.split('/')[2]
|
const pathSegment = route.path.split('/')[2]
|
||||||
// console.log('当前路由分段:', pathSegment)
|
// console.log('当前路由分段:', pathSegment)
|
||||||
|
|
||||||
switch (pathSegment) {
|
switch (pathSegment) {
|
||||||
case 'aericleattribute':
|
case 'aericleattribute':
|
||||||
// 按属性类型获取文章
|
// 按属性类型获取文章
|
||||||
@@ -97,12 +100,13 @@ const getArticlesByRoute = async () => {
|
|||||||
case 'aericletitle':
|
case 'aericletitle':
|
||||||
// 按标题搜索文章
|
// 按标题搜索文章
|
||||||
const titleData = globalStore.getValue('articleserarch')
|
const titleData = globalStore.getValue('articleserarch')
|
||||||
console.log('按标题搜索文章:', titleData.name)
|
// console.log('按标题搜索文章:', titleData.name)
|
||||||
return await articleService.getPagedArticles({title: titleData?.name}, pageNum.value, pageSize.value)
|
return await articleService.getPagedArticles({title: titleData?.name}, pageNum.value, pageSize.value)
|
||||||
case 'aericlestatus':
|
case 'aericlestatus':
|
||||||
// 按状态获取文章
|
// 按状态获取文章
|
||||||
const statusData = globalStore.getValue('articlestatus')
|
const statusData = globalStore.getValue('articlestatus')
|
||||||
return await articleService.getPagedArticles({status: statusData?.status}, pageNum.value, pageSize.value)
|
// console.log('按状态获取文章:', statusData.status)
|
||||||
|
return await articleService.getPagedArticles({status: statusData.status}, pageNum.value, pageSize.value)
|
||||||
default:
|
default:
|
||||||
// 默认获取所有文章
|
// 默认获取所有文章
|
||||||
// console.log('获取所有文章列表')
|
// console.log('获取所有文章列表')
|
||||||
@@ -110,50 +114,6 @@ const getArticlesByRoute = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 为单篇文章补充额外信息(留言数量、分类名称等)
|
|
||||||
* @param {Object} article - 文章对象
|
|
||||||
* @returns {Promise<Object>} 补充信息后的文章对象
|
|
||||||
*/
|
|
||||||
const enrichArticleWithExtraInfo = async (article) => {
|
|
||||||
try {
|
|
||||||
// 获取留言数量
|
|
||||||
const messageResponse = await messageService.getMessagesByArticleId(article.articleid)
|
|
||||||
// 获取分类名称
|
|
||||||
const categoryResponse = await categoryAttributeService.getAttributeById(article.attributeid)
|
|
||||||
|
|
||||||
// 设置分类名称
|
|
||||||
article.categoryName = categoryResponse?.data?.attributename || '未分类'
|
|
||||||
// 设置评论数量
|
|
||||||
article.commentCount = messageResponse?.data?.length || 0
|
|
||||||
// 标准化标记字段名
|
|
||||||
article.marked = article.mg
|
|
||||||
|
|
||||||
return article
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`获取文章${article.articleid}额外信息失败:`, err)
|
|
||||||
// 错误情况下设置默认值
|
|
||||||
article.commentCount = 0
|
|
||||||
article.categoryName = '未分类'
|
|
||||||
return article
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量为文章列表补充额外信息
|
|
||||||
* @param {Array} articles - 原始文章列表
|
|
||||||
* @returns {Promise<Array>} 补充信息后的文章列表
|
|
||||||
*/
|
|
||||||
const enrichArticlesWithExtraInfo = async (articles) => {
|
|
||||||
const enrichedArticles = []
|
|
||||||
|
|
||||||
for (const article of articles) {
|
|
||||||
const enrichedArticle = await enrichArticleWithExtraInfo(article)
|
|
||||||
enrichedArticles.push(enrichedArticle)
|
|
||||||
}
|
|
||||||
|
|
||||||
return enrichedArticles
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -161,7 +121,6 @@ const enrichArticlesWithExtraInfo = async (articles) => {
|
|||||||
*/
|
*/
|
||||||
const fetchArticles = async () => {
|
const fetchArticles = async () => {
|
||||||
let response = {}
|
let response = {}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|
||||||
@@ -170,16 +129,17 @@ const fetchArticles = async () => {
|
|||||||
// console.log('更新后的文章列表:', response)
|
// console.log('更新后的文章列表:', response)
|
||||||
|
|
||||||
// 2. 确保数据存在
|
// 2. 确保数据存在
|
||||||
if (!response.data.content || !Array.isArray(response.data.content)) {
|
if (!response.data || !Array.isArray(response.data)) {
|
||||||
articleList.value = []
|
articleList.value = []
|
||||||
|
totalPages.value = 0
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 为文章列表补充额外信息
|
// 3. 为文章列表补充额外信息
|
||||||
const enrichedArticles = await enrichArticlesWithExtraInfo(response.data.content)
|
console.log('补充额外信息后的文章列表:', response.data)
|
||||||
|
|
||||||
// 4. 更新文章列表
|
// 4. 更新文章列表
|
||||||
articleList.value = enrichedArticles
|
articleList.value = response.data
|
||||||
|
// 5. 更新总页数
|
||||||
|
totalPages.value = response.totalPages
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取文章列表失败:', error)
|
console.error('获取文章列表失败:', error)
|
||||||
ElMessage.error('获取文章列表失败,请稍后重试')
|
ElMessage.error('获取文章列表失败,请稍后重试')
|
||||||
@@ -197,13 +157,10 @@ const fetchArticles = async () => {
|
|||||||
const handleArticleClick = (article) => {
|
const handleArticleClick = (article) => {
|
||||||
try {
|
try {
|
||||||
// 增加文章浏览量(异步操作,不阻塞后续流程)
|
// 增加文章浏览量(异步操作,不阻塞后续流程)
|
||||||
articleService.incrementArticleViews(article.articleId).catch(err => {
|
articleService.incrementArticleViews(article.articleid).catch(err => {
|
||||||
console.error('增加文章浏览量失败:', err)
|
console.error('增加文章浏览量失败:', err)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 清除之前的文章信息
|
|
||||||
globalStore.removeValue('articleInfo')
|
|
||||||
|
|
||||||
// 存储文章信息到全局状态
|
// 存储文章信息到全局状态
|
||||||
globalStore.setValue('articleInfo', article)
|
globalStore.setValue('articleInfo', article)
|
||||||
|
|
||||||
@@ -221,18 +178,8 @@ const handleArticleClick = (article) => {
|
|||||||
* @param {number} newPage - 新的页码
|
* @param {number} newPage - 新的页码
|
||||||
*/
|
*/
|
||||||
const changePage = (newPage) => {
|
const changePage = (newPage) => {
|
||||||
|
pageNum.value = newPage
|
||||||
fetchArticles()
|
fetchArticles()
|
||||||
// 根据当前页码优化分页布局
|
|
||||||
if (page === 1) {
|
|
||||||
// 第一页只显示页码和下一页按钮
|
|
||||||
pageLayout.value = 'pager, next'
|
|
||||||
} else if (page === totalPages.value) {
|
|
||||||
// 最后一页只显示上一页按钮和页码
|
|
||||||
pageLayout.value = 'prev, pager'
|
|
||||||
} else {
|
|
||||||
// 中间页显示完整的上一页、页码、下一页
|
|
||||||
pageLayout.value = 'prev, pager, next'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// ========== 生命周期和监听器 ==========
|
// ========== 生命周期和监听器 ==========
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
<div class="comment-actions-bar">
|
<div class="comment-actions-bar">
|
||||||
<span class="like-button" v-if="false" @click="handleLike(comment)">
|
<span class="like-button" v-if="false" @click="handleLike(comment)">
|
||||||
<span v-if="comment.likes && comment.likes > 0" class="like-count">{{ comment.likes
|
<span v-if="comment.likes && comment.likes > 0" class="like-count">{{ comment.likes
|
||||||
}}</span>
|
}}</span>
|
||||||
👍 赞
|
👍 赞
|
||||||
</span>
|
</span>
|
||||||
<span class="reply-button" @click="handleReply(null, comment)">回复</span>
|
<span class="reply-button" @click="handleReply(null, comment)">回复</span>
|
||||||
@@ -40,9 +40,9 @@
|
|||||||
v-if="globalStore.Login">删除</span>
|
v-if="globalStore.Login">删除</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- 回复列表 -->
|
<!-- 回复列表 -->
|
||||||
<div v-if="comment.replies && comment.replies && comment.replies.length > 0"
|
<div v-if="comment.children && comment.children && comment.children.length > 0"
|
||||||
class="reply-list-container">
|
class="reply-list-container">
|
||||||
<div v-for="reply in comment.replies" :key="reply.messageid" class="reply-item-wrapper">
|
<div v-for="reply in comment.children" :key="reply.messageid" class="reply-item-wrapper">
|
||||||
<div class="reply-header-info">
|
<div class="reply-header-info">
|
||||||
<div class="avatar-container">
|
<div class="avatar-container">
|
||||||
<img v-if="getAvatarUrl(reply.messageimg)" :src="getAvatarUrl(reply.messageimg)"
|
<img v-if="getAvatarUrl(reply.messageimg)" :src="getAvatarUrl(reply.messageimg)"
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="user-meta-info">
|
<div class="user-meta-info">
|
||||||
<div class="user-nickname">{{ reply.displayName || reply.nickname }}</div>
|
<div class="user-nickname">{{processMessageData(reply) }}</div>
|
||||||
<div class="comment-time">{{ formatDate(reply.createdAt) || '刚刚' }}</div>
|
<div class="comment-time">{{ formatDate(reply.createdAt) || '刚刚' }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
|
|
||||||
<span class="like-button" v-if="false" @click="handleLike(reply)">
|
<span class="like-button" v-if="false" @click="handleLike(reply)">
|
||||||
<span v-if="reply.likes && reply.likes > 0" class="like-count">{{ reply.likes
|
<span v-if="reply.likes && reply.likes > 0" class="like-count">{{ reply.likes
|
||||||
}}</span>
|
}}</span>
|
||||||
👍 赞
|
👍 赞
|
||||||
</span>
|
</span>
|
||||||
<span class="reply-button" @click="handleReply(comment, reply)">回复</span>
|
<span class="reply-button" @click="handleReply(comment, reply)">回复</span>
|
||||||
@@ -70,19 +70,23 @@
|
|||||||
<span class="delete-button" @click="handleDelete(reply.messageid)"
|
<span class="delete-button" @click="handleDelete(reply.messageid)"
|
||||||
v-if="globalStore.Login">删除</span>
|
v-if="globalStore.Login">删除</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 无留言提示 -->
|
<!-- 无留言提示 -->
|
||||||
<div v-if="!loading && messageBoardData.length === 0" class="empty-message-state">
|
<div v-if="!messageBoardData" class="empty-message-state">
|
||||||
还没有留言,快来抢沙发吧!
|
还没有留言,快来抢沙发吧!
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 分页按钮 -->
|
<!-- 分页按钮 -->
|
||||||
<div class="pagination-controls" v-if="totalPages > 1">
|
<CustomPagination
|
||||||
<el-pagination size="medium" background :layout="pageLayout" v-model:current-page="pageNum" hide-on-single-page="true" @current-change="changePage" :page-size="pageSize" :page-count="totalPages" class="mt-4" />
|
v-model:pageNum="pageNum"
|
||||||
</div>
|
:pageSize="pageSize"
|
||||||
|
:totalPages="totalPages"
|
||||||
|
@pageChange="changePage"
|
||||||
|
/>
|
||||||
<!-- 留言输入区 -->
|
<!-- 留言输入区 -->
|
||||||
<div class="comment-form-section">
|
<div class="comment-form-section">
|
||||||
<h2 class="comment-form-title">发送评论(请正确填写邮箱地址,否则将会当成垃圾评论处理)</h2>
|
<h2 class="comment-form-title">发送评论(请正确填写邮箱地址,否则将会当成垃圾评论处理)</h2>
|
||||||
@@ -138,6 +142,9 @@ import { messageService } from '@/services'
|
|||||||
import { ElMessage, ElForm } from 'element-plus'
|
import { ElMessage, ElForm } from 'element-plus'
|
||||||
import { useGlobalStore } from '@/store/globalStore'
|
import { useGlobalStore } from '@/store/globalStore'
|
||||||
import { formatDate } from '@/utils/dateUtils'
|
import { formatDate } from '@/utils/dateUtils'
|
||||||
|
import CustomPagination from '@/components/CustomPagination.vue'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ============================== 组件初始化 ==============================
|
// ============================== 组件初始化 ==============================
|
||||||
// 定义组件属性
|
// 定义组件属性
|
||||||
@@ -168,7 +175,6 @@ const showCaptchaHint = ref(false) // 是否显示验证码提示
|
|||||||
const pageNum = ref(1) // 当前页码
|
const pageNum = ref(1) // 当前页码
|
||||||
const pageSize = ref(5) // 每页数量
|
const pageSize = ref(5) // 每页数量
|
||||||
const totalPages = ref(0) // 总页数
|
const totalPages = ref(0) // 总页数
|
||||||
const pageLayout = ref('pager, next')// 分页布局
|
|
||||||
// 表单数据
|
// 表单数据
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
parentid: null, // 父留言ID
|
parentid: null, // 父留言ID
|
||||||
@@ -340,91 +346,6 @@ const getLetterAvatarStyle = (name) => {
|
|||||||
|
|
||||||
// ============================== 数据处理模块 ==============================
|
// ============================== 数据处理模块 ==============================
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理留言数据,构建留言与回复的层级结构
|
|
||||||
* @param {Array} messages - 原始留言数据
|
|
||||||
* @returns {Array} - 处理后的留言数据(包含回复数组)
|
|
||||||
*/
|
|
||||||
const processMessageData = (messages) => {
|
|
||||||
// 为主留言添加replies数组
|
|
||||||
const allMessagesWithReplies = messages.map(msg => ({
|
|
||||||
...msg,
|
|
||||||
replies: []
|
|
||||||
}))
|
|
||||||
|
|
||||||
// 分离主留言和回复
|
|
||||||
const mainMessages = []
|
|
||||||
const replies = []
|
|
||||||
|
|
||||||
allMessagesWithReplies.forEach(msg => {
|
|
||||||
if (msg.parentid && msg.parentid > 0) {
|
|
||||||
replies.push(msg)
|
|
||||||
} else {
|
|
||||||
mainMessages.push(msg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 将回复添加到对应的主留言中并处理@回复显示
|
|
||||||
processRepliesForMainMessages(mainMessages, replies)
|
|
||||||
|
|
||||||
return mainMessages
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理回复数据,将其添加到对应的主留言中
|
|
||||||
* @param {Array} mainMessages - 主留言数组
|
|
||||||
* @param {Array} replies - 回复数组
|
|
||||||
*/
|
|
||||||
const processRepliesForMainMessages = (mainMessages, replies) => {
|
|
||||||
replies.forEach(reply => {
|
|
||||||
// 找到父留言
|
|
||||||
const parentMsg = mainMessages.find(msg => msg.messageid === reply.parentid)
|
|
||||||
if (parentMsg) {
|
|
||||||
// 处理@回复的显示名称
|
|
||||||
processReplyDisplayName(reply, replies)
|
|
||||||
parentMsg.replies.push(reply)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理回复的显示名称
|
|
||||||
* 如果是回复其他回复,添加@标记
|
|
||||||
* @param {Object} reply - 回复对象
|
|
||||||
* @param {Array} allReplies - 所有回复数组
|
|
||||||
*/
|
|
||||||
const processReplyDisplayName = (reply, allReplies) => {
|
|
||||||
if (reply.replyid) {
|
|
||||||
const repliedMsg = allReplies.find(msg => msg.messageid === reply.replyid)
|
|
||||||
if (repliedMsg) {
|
|
||||||
reply.displayName = `${reply.nickname}@ ${repliedMsg.nickname}`
|
|
||||||
} else {
|
|
||||||
reply.displayName = reply.nickname
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
reply.displayName = reply.nickname
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================== API调用模块 ==============================
|
|
||||||
/**
|
|
||||||
* 切换分页
|
|
||||||
* @param {number} page - 目标页码
|
|
||||||
*/
|
|
||||||
const changePage = (page) => {
|
|
||||||
fetchMessages()
|
|
||||||
// 根据当前页码优化分页布局
|
|
||||||
if (page === 1) {
|
|
||||||
// 第一页只显示页码和下一页按钮
|
|
||||||
pageLayout.value = 'pager, next'
|
|
||||||
} else if (page === totalPages.value) {
|
|
||||||
// 最后一页只显示上一页按钮和页码
|
|
||||||
pageLayout.value = 'prev, pager'
|
|
||||||
} else {
|
|
||||||
// 中间页显示完整的上一页、页码、下一页
|
|
||||||
pageLayout.value = 'prev, pager, next'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* 从后端获取留言列表
|
* 从后端获取留言列表
|
||||||
* 根据articleid决定获取文章留言还是全局留言
|
* 根据articleid决定获取文章留言还是全局留言
|
||||||
@@ -437,33 +358,15 @@ const fetchMessages = async () => {
|
|||||||
// 获取文章ID(优先使用props,其次使用全局状态)
|
// 获取文章ID(优先使用props,其次使用全局状态)
|
||||||
const articleid = getArticleId()
|
const articleid = getArticleId()
|
||||||
form.articleid = articleid
|
form.articleid = articleid
|
||||||
// 获取留言数量
|
|
||||||
messageService.getMessageCountByArticleId(articleid).then(res => {
|
|
||||||
if (res.code === 200) {
|
|
||||||
totalPages.value = Math.ceil(res.data / pageSize.value)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
res = await (messageService.getMessagesByPage(articleid, pageNum.value - 1, pageSize.value))
|
res = await (messageService.getMessagesByPage(articleid, pageNum.value - 1, pageSize.value))
|
||||||
// 验证响应结果
|
// 验证响应结果
|
||||||
if (!res || !res.data) {
|
if (res.code != 200) {
|
||||||
handleEmptyResponse()
|
handleEmptyResponse()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理留言数据
|
// 处理留言数据
|
||||||
messageBoardData.value = processMessageData(res.data)
|
messageBoardData.value = res.data
|
||||||
|
totalPages.value = res.totalPages
|
||||||
// 根据当前页码更新分页布局
|
|
||||||
if (pageNum.value === 1) {
|
|
||||||
// 第一页只显示页码和下一页按钮
|
|
||||||
pageLayout.value = 'pager, next'
|
|
||||||
} else if (pageNum.value === totalPages.value) {
|
|
||||||
// 最后一页只显示上一页按钮和页码
|
|
||||||
pageLayout.value = 'prev, pager'
|
|
||||||
} else {
|
|
||||||
// 中间页显示完整的上一页、页码、下一页
|
|
||||||
pageLayout.value = 'prev, pager, next'
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleFetchError(error)
|
handleFetchError(error)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -471,6 +374,30 @@ const fetchMessages = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理留言数据
|
||||||
|
* @param {Array} messages - 原始留言数据
|
||||||
|
* @returns {Array} - 处理后的留言数据(包含回复数组)
|
||||||
|
*/
|
||||||
|
const processMessageData = (messages) => {
|
||||||
|
if (messages.replyToNickname !== null) {
|
||||||
|
return `${messages.nickname} @ ${messages.replyToNickname}`
|
||||||
|
}
|
||||||
|
return messages.nickname
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================== API调用模块 ==============================
|
||||||
|
/**
|
||||||
|
* 切换分页
|
||||||
|
* @param {number} page - 目标页码
|
||||||
|
*/
|
||||||
|
const changePage = (page) => {
|
||||||
|
pageNum.value = page
|
||||||
|
fetchMessages()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取文章ID
|
* 获取文章ID
|
||||||
* 优先级:props.comments > globalStore中的articleInfo
|
* 优先级:props.comments > globalStore中的articleInfo
|
||||||
@@ -478,7 +405,6 @@ const fetchMessages = async () => {
|
|||||||
*/
|
*/
|
||||||
const getArticleId = () => {
|
const getArticleId = () => {
|
||||||
let articleid = props.comments || null
|
let articleid = props.comments || null
|
||||||
|
|
||||||
if (!articleid) {
|
if (!articleid) {
|
||||||
// 安全获取文章ID,如果globalStore中没有articleInfo则返回null
|
// 安全获取文章ID,如果globalStore中没有articleInfo则返回null
|
||||||
const articleData = globalStore.getValue('articleInfo')
|
const articleData = globalStore.getValue('articleInfo')
|
||||||
@@ -486,24 +412,9 @@ const getArticleId = () => {
|
|||||||
? articleData.articleid
|
? articleData.articleid
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
|
||||||
return articleid
|
return articleid
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取所有留言并过滤
|
|
||||||
* 只保留articleid为空或不存在的全局留言
|
|
||||||
* @returns {Promise} - API响应
|
|
||||||
*/
|
|
||||||
const fetchAllMessages = async () => {
|
|
||||||
const res = await messageService.getAllMessages()
|
|
||||||
// 过滤掉articleid不为空的留言,只保留articleid为空或不存在的留言
|
|
||||||
if (res && res.data) {
|
|
||||||
|
|
||||||
res.data = res.data.filter(msg => !msg.articleid || msg.articleid === '')
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理空响应
|
* 处理空响应
|
||||||
|
|||||||
@@ -30,9 +30,15 @@
|
|||||||
:style="getCharStyle(item.id, index)">{{ char }}</span>
|
:style="getCharStyle(item.id, index)">{{ char }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
// 分页区域
|
<!-- 分页区域 -->
|
||||||
<PaginationComponent class="pagination-container" :list="nonsenseList" :pageSize="10"
|
<div class="pagination-container">
|
||||||
@changePage="handleCurrentDataUpdate" />
|
<CustomPagination
|
||||||
|
v-model:page-num="pageNum"
|
||||||
|
:page-size="pageSize"
|
||||||
|
:total-pages="totalPages"
|
||||||
|
@page-change="handlePageChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -42,9 +48,9 @@ import { ref, onMounted, onBeforeUnmount } from 'vue'
|
|||||||
import { nonsenseService } from '@/services'
|
import { nonsenseService } from '@/services'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { ElMessageBox } from 'element-plus'
|
import { ElMessageBox } from 'element-plus'
|
||||||
import PaginationComponent from '@/views/page.vue'
|
|
||||||
import { formatRelativeTime } from '@/utils/dateUtils'
|
import { formatRelativeTime } from '@/utils/dateUtils'
|
||||||
import { useGlobalStore } from '@/store/globalStore'
|
import { useGlobalStore } from '@/store/globalStore'
|
||||||
|
import CustomPagination from '@/components/CustomPagination.vue'
|
||||||
const globalStore = useGlobalStore()
|
const globalStore = useGlobalStore()
|
||||||
/**
|
/**
|
||||||
* 吐槽数据列表
|
* 吐槽数据列表
|
||||||
@@ -61,15 +67,21 @@ const displayedNonsenseList = ref([])
|
|||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
// 错误状态
|
// 错误状态
|
||||||
const error = ref(false)
|
const error = ref(false)
|
||||||
|
// 分页相关
|
||||||
|
const pageNum = ref(1) // 当前页码
|
||||||
|
const pageSize = ref(5) // 每页数量
|
||||||
|
const totalPages = ref(0) // 总页数
|
||||||
|
|
||||||
// 处理分页数据更新
|
// 处理页码变化
|
||||||
const handleCurrentDataUpdate = (data) => {
|
const handlePageChange = (newPage) => {
|
||||||
displayedNonsenseList.value = data
|
pageNum.value = newPage
|
||||||
// console.log(data)
|
loadNonsenseList()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重试加载
|
// 重试加载
|
||||||
const handleRetry = () => {
|
const handleRetry = () => {
|
||||||
|
// 重置分页参数
|
||||||
|
pageNum.value = 1
|
||||||
|
totalPages.value = 0
|
||||||
error.value = false
|
error.value = false
|
||||||
loadNonsenseList()
|
loadNonsenseList()
|
||||||
}
|
}
|
||||||
@@ -85,9 +97,22 @@ const loadNonsenseList = async () => {
|
|||||||
loading.value = true
|
loading.value = true
|
||||||
error.value = false
|
error.value = false
|
||||||
try {
|
try {
|
||||||
const response = await nonsenseService.getNonsenseByStatus(1)
|
const response = await nonsenseService.getNonsenseByStatus({
|
||||||
|
status: 1,
|
||||||
|
pageNum: pageNum.value - 1,
|
||||||
|
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
|
||||||
|
// 显示当前页的内容
|
||||||
|
displayedNonsenseList.value = response.data
|
||||||
|
// 初始化字符样式
|
||||||
|
// initializeCharStyles()
|
||||||
|
// 开始颜色变化定时器
|
||||||
|
console.log(displayedNonsenseList.value)
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error('加载吐槽内容失败')
|
ElMessage.error('加载吐槽内容失败')
|
||||||
error.value = true
|
error.value = true
|
||||||
|
|||||||
@@ -1,199 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="pagination-container">
|
|
||||||
<!-- 数据渲染区域 -->
|
|
||||||
|
|
||||||
<!-- 分页按钮区域 - 只在需要分页时显示(totalPages > 0 且 list.length <= pageSize) -->
|
|
||||||
<div class="pagination-controls" v-if="totalPages > 1 ">
|
|
||||||
<button
|
|
||||||
v-for="page in totalPages"
|
|
||||||
:key="page"
|
|
||||||
:class="['pagination-btn', { active: currentPage === page }]"
|
|
||||||
@click="changePage(page)"
|
|
||||||
>
|
|
||||||
{{ page }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, computed, watch, onMounted } from 'vue'
|
|
||||||
|
|
||||||
// 定义组件名称
|
|
||||||
defineOptions({
|
|
||||||
name: 'PaginationComponent'
|
|
||||||
})
|
|
||||||
|
|
||||||
// 定义props
|
|
||||||
interface Props {
|
|
||||||
list: any[]
|
|
||||||
pageSize?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
|
||||||
list: () => [],
|
|
||||||
pageSize: 10
|
|
||||||
})
|
|
||||||
|
|
||||||
// 定义响应式数据
|
|
||||||
const currentPage = ref(1)
|
|
||||||
const groupedData = ref<any[]>([])
|
|
||||||
|
|
||||||
// 定义事件
|
|
||||||
const emit = defineEmits<{
|
|
||||||
changePage: [data: any[]]
|
|
||||||
}>()
|
|
||||||
|
|
||||||
// 计算属性 - 计算总页数
|
|
||||||
const totalPages = computed(() => {
|
|
||||||
// 如果列表为空,返回0
|
|
||||||
if (!props.list || props.list.length === 0) return 0
|
|
||||||
// 如果列表长度小于pageSize,不进行分组,返回0表示不分页
|
|
||||||
if (props.list.length <= props.pageSize) return 0
|
|
||||||
// console.log(props.list.length, props.pageSize)
|
|
||||||
|
|
||||||
// 列表长度小于等于pageSize时,正常计算页数
|
|
||||||
// 如果能整除,直接返回商
|
|
||||||
if (props.list.length % props.pageSize === 0) {
|
|
||||||
return props.list.length / props.pageSize
|
|
||||||
}
|
|
||||||
|
|
||||||
// 不能整除时,返回商+1(用户需求:将余数添加到最后一组)
|
|
||||||
return Math.floor(props.list.length / props.pageSize) + 1
|
|
||||||
})
|
|
||||||
|
|
||||||
// 计算当前页数据
|
|
||||||
const currentPageData = computed(() => {
|
|
||||||
// 如果列表为空,返回空数组
|
|
||||||
if (!props.list || props.list.length === 0) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果列表长度小于pageSize,不分组,直接返回完整列表
|
|
||||||
if (props.list.length <= props.pageSize) {
|
|
||||||
return [...props.list]
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果总页数为0(列表为空的情况已处理),返回空数组
|
|
||||||
if (totalPages.value === 0) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确保当前页码不超过总页数
|
|
||||||
if (currentPage.value > totalPages.value) {
|
|
||||||
currentPage.value = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// 正常分组逻辑
|
|
||||||
const startIndex = (currentPage.value - 1) * props.pageSize
|
|
||||||
let endIndex = startIndex + props.pageSize
|
|
||||||
|
|
||||||
// 对于最后一页,确保不会超出列表范围
|
|
||||||
if (currentPage.value === totalPages.value) {
|
|
||||||
endIndex = props.list.length
|
|
||||||
}
|
|
||||||
|
|
||||||
return props.list.slice(startIndex, endIndex)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 方法 - 重新实现分组逻辑
|
|
||||||
const groupData = () => {
|
|
||||||
groupedData.value = []
|
|
||||||
|
|
||||||
// 如果列表为空或长度大于pageSize,不进行分组
|
|
||||||
if (!props.list || props.list.length === 0 || props.list.length > props.pageSize) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const listLength = props.list.length
|
|
||||||
|
|
||||||
// 正常分组逻辑,余数会自动添加到最后一组
|
|
||||||
for (let i = 0; i < listLength; i += props.pageSize) {
|
|
||||||
groupedData.value.push(props.list.slice(i, i + props.pageSize))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const changePage = (page: number) => {
|
|
||||||
// 只有在需要分页的情况下才处理页码变化
|
|
||||||
if (totalPages.value > 0 && page >= 1 && page <= totalPages.value) {
|
|
||||||
currentPage.value = page
|
|
||||||
// 发出事件,传递当前页数据
|
|
||||||
emit('changePage', currentPageData.value)
|
|
||||||
} else if (props.list && props.list.length > 0) {
|
|
||||||
// 当不分组时,直接传递完整列表
|
|
||||||
emit('changePage', [...props.list])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 监听数据变化
|
|
||||||
watch(() => props.list, () => {
|
|
||||||
currentPage.value = 1
|
|
||||||
groupData()
|
|
||||||
// 数据变化时,立即发出当前数据
|
|
||||||
emit('changePage', currentPageData.value)
|
|
||||||
}, { deep: true })
|
|
||||||
|
|
||||||
// 生命周期钩子
|
|
||||||
onMounted(() => {
|
|
||||||
groupData()
|
|
||||||
// 组件挂载时,立即发出初始数据
|
|
||||||
emit('changePage', currentPageData.value)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.pagination-container {
|
|
||||||
width: 100%;
|
|
||||||
padding: 20px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-content {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-item {
|
|
||||||
padding: 10px;
|
|
||||||
border: 1px solid #e0e0e0;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-data {
|
|
||||||
text-align: center;
|
|
||||||
color: #999;
|
|
||||||
padding: 40px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination-controls {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 10px;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination-btn {
|
|
||||||
padding: 8px 16px;
|
|
||||||
border: 1px solid #d0d0d0;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination-btn:hover {
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination-btn.active {
|
|
||||||
background-color: #1890ff;
|
|
||||||
color: #fff;
|
|
||||||
border-color: #1890ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination-btn.active:hover {
|
|
||||||
background-color: #40a9ff;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -31,7 +31,7 @@ export default defineConfig({
|
|||||||
// 配置API代理
|
// 配置API代理
|
||||||
'/api': {
|
'/api': {
|
||||||
// target: 'http://www.qf1121.top',
|
// target: 'http://www.qf1121.top',
|
||||||
target: 'http://localhost:7071',
|
target: 'http://localhost:7070',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (path) => path
|
rewrite: (path) => path
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user