- 添加文章相关API配置和模型 - 创建文章列表页和详情页组件 - 实现文章列表展示和详情查看功能 - 在底部导航栏添加文章入口 - 新增文章列表项组件master
@ -0,0 +1,31 @@ | |||
/** | |||
* 文章相关接口配置 | |||
* 对应后端 YaoDuArticleController | |||
*/ | |||
const api = { | |||
/** | |||
* 获取文章列表 | |||
* 对应后端: GET /city/article/list | |||
* @param {String} title - 文章标题(搜索关键词) | |||
* @param {Object} bean - 分页排序参数 (包含 current, size 等分页信息) | |||
*/ | |||
articleList: { | |||
url: '/city/article/list', | |||
method: 'GET', | |||
auth: false, | |||
showLoading: false | |||
}, | |||
/** | |||
* 根据ID获取文章详情 | |||
* 对应后端: GET /city/article/queryById | |||
* @param {String} id - 文章ID | |||
*/ | |||
articleDetail: { | |||
url: '/city/article/queryById', | |||
method: 'GET', | |||
auth: false, | |||
} | |||
} | |||
export default api |
@ -0,0 +1,76 @@ | |||
<template> | |||
<view class="article-item" @click="handleClick"> | |||
<view class="image-container"> | |||
<image :src="item.image" mode="aspectFill" class="article-image" /> | |||
</view> | |||
<view class="content-container"> | |||
<text class="article-title">{{ item.title }}</text> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
export default { | |||
name: 'ArticleItem', | |||
props: { | |||
item: { | |||
type: Object, | |||
required: true, | |||
default: () => ({ | |||
title: '', | |||
image: '' | |||
}) | |||
} | |||
}, | |||
methods: { | |||
handleClick() { | |||
this.$emit('click', this.item); | |||
} | |||
} | |||
} | |||
</script> | |||
<style scoped lang="scss"> | |||
.article-item { | |||
display: flex; | |||
flex-direction: column; | |||
background: #fff; | |||
border-radius: 12rpx; | |||
overflow: hidden; | |||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1); | |||
margin-bottom: 20rpx; | |||
transition: transform 0.2s ease; | |||
&:active { | |||
transform: scale(0.98); | |||
} | |||
} | |||
.image-container { | |||
width: 100%; | |||
height: 300rpx; | |||
overflow: hidden; | |||
.article-image { | |||
width: 100%; | |||
height: 100%; | |||
object-fit: cover; | |||
} | |||
} | |||
.content-container { | |||
padding: 20rpx; | |||
.article-title { | |||
font-size: 30rpx; | |||
font-weight: 500; | |||
color: #333; | |||
line-height: 1.6; | |||
display: -webkit-box; | |||
-webkit-box-orient: vertical; | |||
-webkit-line-clamp: 2; | |||
overflow: hidden; | |||
text-overflow: ellipsis; | |||
} | |||
} | |||
</style> |
@ -0,0 +1,99 @@ | |||
<template> | |||
<view class="page"> | |||
<navbar title="江华"/> | |||
<view class="content"> | |||
<view class="article-list" v-if="!loading"> | |||
<article-item | |||
v-for="(item, index) in List" | |||
:key="item.id || index" | |||
:item="item" | |||
@click="handleItemClick" | |||
/> | |||
</view> | |||
<!-- 加载状态 --> | |||
<view class="loading-container" v-if="loading"> | |||
<text class="loading-text">加载中...</text> | |||
</view> | |||
<!-- 空状态 --> | |||
<view class="empty-container" v-if="!loading && List.length === 0"> | |||
<text class="empty-text">暂无文章</text> | |||
</view> | |||
</view> | |||
<tabber select="1" /> | |||
</view> | |||
</template> | |||
<script> | |||
import tabber from '@/components/base/tabbar.vue' | |||
import articleItem from '@/components/list/articleItem.vue' | |||
import loadList from '@/mixins/loadList.js' | |||
export default { | |||
mixins: [loadList], | |||
components : { | |||
tabber, | |||
articleItem | |||
}, | |||
data() { | |||
return { | |||
mixinsListApi: 'articleList' | |||
} | |||
}, | |||
methods: { | |||
// 处理文章点击事件 | |||
handleItemClick(item) { | |||
console.log('点击了文章:', item); | |||
// 跳转到文章详情页 | |||
uni.navigateTo({ | |||
url: `/pages_order/article/index?id=${item.id}` | |||
}) | |||
} | |||
} | |||
} | |||
</script> | |||
<style scoped lang="scss"> | |||
.page { | |||
background-color: #f5f5f5; | |||
min-height: 100vh; | |||
} | |||
.content { | |||
padding: 20rpx; | |||
padding-bottom: 120rpx; // 为底部tabbar留出空间 | |||
} | |||
.article-list { | |||
display: flex; | |||
flex-direction: column; | |||
gap: 20rpx; | |||
} | |||
.loading-container { | |||
display: flex; | |||
justify-content: center; | |||
align-items: center; | |||
padding: 100rpx 0; | |||
.loading-text { | |||
font-size: 28rpx; | |||
color: #999; | |||
} | |||
} | |||
.empty-container { | |||
display: flex; | |||
justify-content: center; | |||
align-items: center; | |||
padding: 100rpx 0; | |||
.empty-text { | |||
font-size: 28rpx; | |||
color: #999; | |||
} | |||
} | |||
</style> |
@ -0,0 +1,224 @@ | |||
<template> | |||
<view class="page"> | |||
<navbar title="文章详情" /> | |||
<view class="content"> | |||
<!-- 加载状态 --> | |||
<view class="loading-container" v-if="loading"> | |||
<text class="loading-text">加载中...</text> | |||
</view> | |||
<!-- 文章详情 --> | |||
<view class="article-detail" v-if="!loading && articleDetail"> | |||
<!-- 创建时间 --> | |||
<view class="article-meta"> | |||
<text class="create-time">{{ formatTime(articleDetail.createTime) }}</text> | |||
</view> | |||
<!-- 富文本内容 --> | |||
<view class="article-content"> | |||
<rich-text :nodes="articleDetail.content"></rich-text> | |||
</view> | |||
</view> | |||
<!-- 错误状态 --> | |||
<view class="error-container" v-if="!loading && !articleDetail"> | |||
<text class="error-text">文章不存在或已被删除</text> | |||
</view> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
export default { | |||
data() { | |||
return { | |||
articleId: '', | |||
articleDetail: null, | |||
loading: false | |||
} | |||
}, | |||
onLoad(options) { | |||
if (options.id) { | |||
this.articleId = options.id; | |||
this.loadArticleDetail(); | |||
} | |||
}, | |||
onShareAppMessage(res) { | |||
return { | |||
title: this.articleDetail ? this.articleDetail.title || '文章详情' : '文章详情', | |||
imageUrl: this.articleDetail ? this.articleDetail.image : '', | |||
path: '/pages_order/article/index?id=' + this.articleId | |||
} | |||
}, | |||
methods: { | |||
// 加载文章详情 | |||
loadArticleDetail() { | |||
if (!this.articleId) return; | |||
this.loading = true; | |||
const params = { | |||
id: this.articleId | |||
}; | |||
this.$api('articleDetail', params, (res) => { | |||
this.loading = false; | |||
if (res.code === 200 && res.result) { | |||
this.articleDetail = res.result; | |||
} else { | |||
uni.showToast({ | |||
title: res.message || '加载失败', | |||
icon: 'none' | |||
}); | |||
} | |||
}); | |||
}, | |||
// 格式化时间 | |||
formatTime(time) { | |||
if (!time) return ''; | |||
const date = new Date(time); | |||
const year = date.getFullYear(); | |||
const month = String(date.getMonth() + 1).padStart(2, '0'); | |||
const day = String(date.getDate()).padStart(2, '0'); | |||
const hours = String(date.getHours()).padStart(2, '0'); | |||
const minutes = String(date.getMinutes()).padStart(2, '0'); | |||
return `${year}-${month}-${day} ${hours}:${minutes}`; | |||
} | |||
} | |||
} | |||
</script> | |||
<style scoped lang="scss"> | |||
.page { | |||
background-color: #f5f5f5; | |||
min-height: 100vh; | |||
} | |||
.content { | |||
padding: 20rpx; | |||
} | |||
.loading-container { | |||
display: flex; | |||
justify-content: center; | |||
align-items: center; | |||
padding: 200rpx 0; | |||
.loading-text { | |||
font-size: 28rpx; | |||
color: #999; | |||
} | |||
} | |||
.error-container { | |||
display: flex; | |||
justify-content: center; | |||
align-items: center; | |||
padding: 200rpx 0; | |||
.error-text { | |||
font-size: 28rpx; | |||
color: #999; | |||
} | |||
} | |||
.article-detail { | |||
background-color: #fff; | |||
border-radius: 16rpx; | |||
padding: 30rpx; | |||
margin-bottom: 20rpx; | |||
} | |||
.article-meta { | |||
padding-bottom: 20rpx; | |||
border-bottom: 1px solid #f0f0f0; | |||
margin-bottom: 30rpx; | |||
.create-time { | |||
font-size: 24rpx; | |||
color: #999; | |||
} | |||
} | |||
.article-content { | |||
line-height: 1.6; | |||
// 富文本内容样式 | |||
:deep(rich-text) { | |||
font-size: 30rpx; | |||
color: #333; | |||
// 图片样式 | |||
img { | |||
max-width: 100%; | |||
height: auto; | |||
border-radius: 8rpx; | |||
margin: 20rpx 0; | |||
} | |||
// 段落样式 | |||
p { | |||
margin: 20rpx 0; | |||
line-height: 1.8; | |||
} | |||
// 标题样式 | |||
h1, h2, h3, h4, h5, h6 { | |||
margin: 30rpx 0 20rpx 0; | |||
font-weight: bold; | |||
} | |||
h1 { font-size: 36rpx; } | |||
h2 { font-size: 34rpx; } | |||
h3 { font-size: 32rpx; } | |||
// 列表样式 | |||
ul, ol { | |||
padding-left: 40rpx; | |||
margin: 20rpx 0; | |||
} | |||
li { | |||
margin: 10rpx 0; | |||
line-height: 1.6; | |||
} | |||
// 引用样式 | |||
blockquote { | |||
border-left: 4rpx solid #ddd; | |||
padding-left: 20rpx; | |||
margin: 20rpx 0; | |||
color: #666; | |||
font-style: italic; | |||
} | |||
// 代码样式 | |||
code { | |||
background-color: #f5f5f5; | |||
padding: 4rpx 8rpx; | |||
border-radius: 4rpx; | |||
font-family: monospace; | |||
font-size: 26rpx; | |||
} | |||
pre { | |||
background-color: #f5f5f5; | |||
padding: 20rpx; | |||
border-radius: 8rpx; | |||
overflow-x: auto; | |||
margin: 20rpx 0; | |||
code { | |||
background: none; | |||
padding: 0; | |||
} | |||
} | |||
} | |||
} | |||
</style> |