Browse Source

feat(article): 新增文章模块功能

- 添加文章相关API配置和模型
- 创建文章列表页和详情页组件
- 实现文章列表展示和详情查看功能
- 在底部导航栏添加文章入口
- 新增文章列表项组件
master
主管理员 2 weeks ago
parent
commit
1f9441367f
8 changed files with 469 additions and 24 deletions
  1. +1
    -1
      api/api.js
  2. +31
    -0
      api/model/article.js
  3. +2
    -2
      components/base/tabbar.vue
  4. +76
    -0
      components/list/articleItem.vue
  5. +13
    -0
      pages.json
  6. +99
    -0
      pages/index/article.vue
  7. +224
    -0
      pages_order/article/index.vue
  8. +23
    -21
      pages_order/post/postDetail.vue

+ 1
- 1
api/api.js View File

@ -527,7 +527,7 @@ const config = {
},
}
const models = ['order', 'group']
const models = ['order', 'group', 'article']
models.forEach(key => {
addApiModel(require(`./model/${key}.js`).default, key)


+ 31
- 0
api/model/article.js View File

@ -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

+ 2
- 2
components/base/tabbar.vue View File

@ -46,8 +46,8 @@
{
"selectedIconPath": "/static/image/tabbar/order-a.png",
"iconPath": "/static/image/tabbar/order.png",
"pagePath": "/pages/index/square",
"title": "广场",
"pagePath": "/pages/index/article",
"title": "江华",
icon : 'integral',
},
{


+ 76
- 0
components/list/articleItem.vue View File

@ -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>

+ 13
- 0
pages.json View File

@ -29,6 +29,12 @@
"style": {
"enablePullDownRefresh" : true
}
},
{
"path": "pages/index/article",
"style": {
"enablePullDownRefresh" : true
}
}
],
"preloadRule": {
@ -232,6 +238,13 @@
"navigationBarTitleText": "创建群组",
"enablePullDownRefresh": false
}
},
{
"path": "article/index",
"style": {
"navigationBarTitleText": "文章详情",
"enablePullDownRefresh": false
}
}
]
}],


+ 99
- 0
pages/index/article.vue View File

@ -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>

+ 224
- 0
pages_order/article/index.vue View File

@ -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>

+ 23
- 21
pages_order/post/postDetail.vue View File

@ -14,6 +14,20 @@
<statisticalDataInfo :item="detail"/>
</view>
<view style="background-color: #fff;margin-top: 20rpx;">
<uv-tabs :list="tags"
:activeStyle="{color : '#000', fontWeight : 900}"
lineColor="#5baaff"
lineHeight="8rpx"
lineWidth="50rpx"
:scrollable="false"
@click="tabsClick"></uv-tabs>
</view>
<view class="" v-if="tagIndex == 1">
<!-- 头像堆叠组件 - 显示查看过的用户 -->
<avatarStack
:avatars="viewedUsers || []"
@ -25,19 +39,7 @@
@moreClick="handleMoreViewers"
v-if="viewedUsers && viewedUsers.length > 0"
/>
</view>
<!-- <view style="background-color: #fff;margin-top: 20rpx;">
<uv-tabs :list="tags"
:activeStyle="{color : '#000', fontWeight : 900}"
lineColor="#5baaff"
lineHeight="8rpx"
lineWidth="50rpx"
:scrollable="false"
@click="tabsClick"></uv-tabs>
</view> -->
<commentList v-if="tagIndex == 0" @getData="getData" :list="list" :params="params" />
@ -123,15 +125,15 @@
{
name : '评论'
},
// {
// name : ''
// },
// {
// name : ''
// },
// {
// name : ''
// },
{
name : '浏览'
},
{
name : '点赞'
},
{
name : '分享'
},
],
tagIndex : 0,
id : 0,


Loading…
Cancel
Save