646 lines
15 KiB
Vue
646 lines
15 KiB
Vue
<template>
|
|
<view class="detail-page" :style="{paddingTop: paddingTop}">
|
|
<view class="detail-bg"></view>
|
|
|
|
<view class="detail-header">
|
|
<view class="header-left" @click="goBack">
|
|
<u-icon name="arrow-left" size="32" color="#1b1b1b"></u-icon>
|
|
</view>
|
|
<text class="header-title">详情</text>
|
|
<!-- <view class="header-right">-->
|
|
<!-- <u-icon name="more-dot-fill" size="26" color="#1b1b1b"></u-icon>-->
|
|
<!-- <u-icon name="calendar-fill" size="26" color="#1b1b1b" class="header-icon-gap"></u-icon>-->
|
|
<!-- </view>-->
|
|
</view>
|
|
|
|
<scroll-view scroll-y class="detail-scroll">
|
|
<view class="detail-info">
|
|
<view class="detail-label">{{ detailInfo.category }}</view>
|
|
<view class="detail-title">{{ detailInfo.title }}</view>
|
|
<view class="detail-subtitle">{{ detailInfo.source }}</view>
|
|
<view class="detail-author">
|
|
<!-- <view class="author-meta">-->
|
|
<!-- <text class="author-name">{{ detailInfo.author }}</text>-->
|
|
<!-- <text class="author-desc">{{ detailInfo.authorDesc }}</text>-->
|
|
<!-- </view>-->
|
|
<text class="detail-date">{{ dayjs(detailInfo.createTime).format('YYYY-MM-DD') }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- <image class="detail-cover" :src="detailInfo.image" mode="aspectFill"></image>-->
|
|
|
|
<view class="detail-article">
|
|
<u-parse :content="detailInfo.content"/>
|
|
</view>
|
|
<u-gap height="200rpx"/>
|
|
</scroll-view>
|
|
|
|
<view class="detail-action">
|
|
<view class="flex-1 flex justify-center items-center">
|
|
<view class="action-item" @click="openCommentPopup" v-if="!authing">
|
|
<u-icon name="chat" size="28" color="#1b1b1b"></u-icon>
|
|
<text class="action-text">评论</text>
|
|
</view>
|
|
<view class="action-item" @click="toggleFavorite">
|
|
<u-icon :name="isFavorite ? 'star-fill' : 'star'" size="28" color="#f7b500"></u-icon>
|
|
<text class="action-text">收藏</text>
|
|
</view>
|
|
</view>
|
|
<button open-type="share" class="action-button" @click="handleShare">
|
|
<u-icon name="share" size="28" color="#fff"></u-icon>
|
|
<text class="action-btn-text">分享</text>
|
|
</button>
|
|
</view>
|
|
|
|
<u-popup
|
|
:show="showCommentPopup"
|
|
mode="bottom"
|
|
round="20"
|
|
close-on-click-overlay
|
|
:safe-area-inset-bottom="false"
|
|
@close="showCommentPopup = false"
|
|
>
|
|
<view class="comment-popup">
|
|
<view class="comment-popup-header">
|
|
<text class="comment-popup-title">评论 ({{ commentList.length }})</text>
|
|
<u-icon name="close" size="28" color="#9ca3af" @click="showCommentPopup = false"></u-icon>
|
|
</view>
|
|
<scroll-view scroll-y class="comment-list">
|
|
<view v-if="commentLoading" class="comment-placeholder">评论加载中...</view>
|
|
<view v-else-if="!commentList.length" class="comment-placeholder">暂时还没有评论</view>
|
|
<view v-else>
|
|
<view
|
|
class="comment-item"
|
|
v-for="(item, index) in commentList"
|
|
:key="item.commentId || item.id || index"
|
|
>
|
|
<view class="comment-item-header">
|
|
<text class="comment-user">{{ item.userName || item.nickname || '匿名用户' }}</text>
|
|
<text class="comment-time">{{ formatCommentTime(item.createTime || item.createdAt) }}</text>
|
|
</view>
|
|
<view class="comment-content">{{ item.content }}</view>
|
|
</view>
|
|
</view>
|
|
</scroll-view>
|
|
<view class="comment-footer">
|
|
<view class="comment-publish-btn" @click="openPublishPopup">发表评论</view>
|
|
</view>
|
|
</view>
|
|
</u-popup>
|
|
|
|
<u-popup
|
|
:show="showPublishPopup"
|
|
mode="center"
|
|
round="20"
|
|
close-on-click-overlay
|
|
:safe-area-inset-bottom="false"
|
|
@close="closePublishPopup"
|
|
>
|
|
<view class="publish-popup">
|
|
<view class="publish-header">写评论</view>
|
|
<textarea
|
|
class="publish-textarea"
|
|
v-model="newCommentContent"
|
|
maxlength="200"
|
|
auto-height
|
|
placeholder="请输入评论内容"
|
|
placeholder-style="color:#9ca3af"
|
|
></textarea>
|
|
<view class="publish-actions">
|
|
<view class="publish-cancel" @click="closePublishPopup">取消</view>
|
|
<view
|
|
class="publish-submit"
|
|
:class="{'is-disabled': commentSubmitting}"
|
|
@click="submitComment"
|
|
>
|
|
发表
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</u-popup>
|
|
|
|
<Login ref="loginPopup" @done="onLoginDone"/>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import {
|
|
addArticleCollectReq,
|
|
articleInfoReq,
|
|
articleCommentListReq,
|
|
addArticleCommentReq, dictDataReq
|
|
} from "@/api/common";
|
|
import {dayjs} from '@/uni_modules/iRainna-dayjs/js_sdk/dayjs.min.js'
|
|
import Login from "@/components/Login.vue";
|
|
import {getUserInfo} from "@/util/user";
|
|
|
|
export default {
|
|
components: {Login},
|
|
data() {
|
|
return {
|
|
detailInfo: {
|
|
category: '',
|
|
title: '',
|
|
subtitle: '',
|
|
author: '',
|
|
authorDesc: '',
|
|
date: '',
|
|
image: '',
|
|
content: []
|
|
},
|
|
isFavorite: false,
|
|
articleImages: [
|
|
'/static/icon/article_1.png',
|
|
'/static/icon/article_2.png',
|
|
'/static/icon/article_3.png',
|
|
'/static/icon/article_4.png',
|
|
'/static/icon/article_5.png',
|
|
'/static/icon/article_6.png'
|
|
],
|
|
mockArticles: [
|
|
{
|
|
id: 5,
|
|
category: '',
|
|
title: '',
|
|
subtitle: '',
|
|
author: '',
|
|
authorDesc: '8年情感心理师 · 自由写作者',
|
|
date: '',
|
|
image: '',
|
|
content: ''
|
|
}
|
|
],
|
|
paddingTop: '40px',
|
|
showCommentPopup: false,
|
|
commentList: [],
|
|
commentLoading: false,
|
|
showPublishPopup: false,
|
|
newCommentContent: '',
|
|
commentSubmitting: false,
|
|
authing: true,
|
|
}
|
|
},
|
|
onLoad(options) {
|
|
const {id} = options || {}
|
|
this.getInfo(id)
|
|
this.paddingTop = uni.getSystemInfoSync().statusBarHeight + 'px'
|
|
},
|
|
onShow(){
|
|
this.getDictList()
|
|
},
|
|
methods: {
|
|
dayjs,
|
|
async getDictList() {
|
|
const {data} = await dictDataReq({dictId: '171'})
|
|
this.authing = parseInt(data[0].dictDataCode) === 1
|
|
},
|
|
async getInfo(id) {
|
|
const {data} = await articleInfoReq(id)
|
|
this.detailInfo = data
|
|
this.isFavorite = data.hasCollect
|
|
},
|
|
goBack() {
|
|
if (getCurrentPages().length === 1) uni.switchTab({url: '/pages/index/index'})
|
|
else uni.navigateBack()
|
|
},
|
|
toggleFavorite() {
|
|
this.isFavorite = !this.isFavorite
|
|
uni.showToast({
|
|
title: this.isFavorite ? '已收藏' : '已取消',
|
|
icon: 'none'
|
|
})
|
|
addArticleCollectReq({
|
|
articleId: this.detailInfo.articleId
|
|
})
|
|
},
|
|
handleShare() {
|
|
uni.showToast({
|
|
title: '已复制分享链接',
|
|
icon: 'none'
|
|
})
|
|
},
|
|
openCommentPopup() {
|
|
if (!this.detailInfo.articleId) return
|
|
this.showCommentPopup = true
|
|
this.fetchComments()
|
|
},
|
|
async fetchComments() {
|
|
if (!this.detailInfo.articleId) return
|
|
this.commentLoading = true
|
|
try {
|
|
const {data} = await articleCommentListReq({articleId: this.detailInfo.articleId})
|
|
this.commentList = this.normalizeCommentList(data)
|
|
} catch (error) {
|
|
console.log(error)
|
|
uni.showToast({
|
|
title: '评论加载失败',
|
|
icon: 'none'
|
|
})
|
|
} finally {
|
|
this.commentLoading = false
|
|
}
|
|
},
|
|
normalizeCommentList(data) {
|
|
if (Array.isArray(data)) return data
|
|
if (data && Array.isArray(data.records)) return data.records
|
|
if (data && Array.isArray(data.list)) return data.list
|
|
if (data && Array.isArray(data.rows)) return data.rows
|
|
return []
|
|
},
|
|
openPublishPopup() {
|
|
if (!this.ensureLogin()) return
|
|
this.showPublishPopup = true
|
|
this.newCommentContent = ''
|
|
},
|
|
closePublishPopup() {
|
|
this.showPublishPopup = false
|
|
},
|
|
async submitComment() {
|
|
if (this.commentSubmitting) return
|
|
if (!this.ensureLogin()) return
|
|
const content = (this.newCommentContent || '').trim()
|
|
if (!content) {
|
|
return uni.showToast({
|
|
title: '请输入评论内容',
|
|
icon: 'none'
|
|
})
|
|
}
|
|
this.commentSubmitting = true
|
|
try {
|
|
await addArticleCommentReq({
|
|
content,
|
|
articleId: this.detailInfo.articleId
|
|
})
|
|
uni.showToast({
|
|
title: '发表成功',
|
|
icon: 'success'
|
|
})
|
|
this.newCommentContent = ''
|
|
this.showPublishPopup = false
|
|
this.fetchComments()
|
|
} catch (error) {
|
|
console.log(error)
|
|
const message = error && (error.msg || error.message)
|
|
uni.showToast({
|
|
title: message || '发表评论失败',
|
|
icon: 'none'
|
|
})
|
|
} finally {
|
|
this.commentSubmitting = false
|
|
}
|
|
},
|
|
ensureLogin() {
|
|
const {token} = getUserInfo()
|
|
if (!token) {
|
|
this.$refs.loginPopup && this.$refs.loginPopup.open()
|
|
return false
|
|
}
|
|
return true
|
|
},
|
|
onLoginDone() {
|
|
if (this.showCommentPopup) {
|
|
this.fetchComments()
|
|
}
|
|
},
|
|
formatCommentTime(time) {
|
|
if (!time) return '--'
|
|
const parsed = dayjs(time)
|
|
return typeof parsed.isValid === 'function' && parsed.isValid() ? parsed.format('YYYY-MM-DD HH:mm') : time
|
|
}
|
|
},
|
|
onShareTimeline() {
|
|
return {
|
|
title: this.detailInfo.title,
|
|
path: '/pages/index/detail?id=' + this.detailInfo.articleId
|
|
}
|
|
},
|
|
onShareAppMessage() {
|
|
return {
|
|
title: this.detailInfo.title,
|
|
path: '/pages/index/detail?id=' + this.detailInfo.articleId
|
|
}
|
|
},
|
|
}
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.detail-page {
|
|
position: relative;
|
|
min-height: 100vh;
|
|
background: linear-gradient(180deg, #e5edff 0%, #f6f4ff 45%, #fef7ff 100%);
|
|
//padding: 60rpx 32rpx 140rpx;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.detail-bg {
|
|
position: absolute;
|
|
left: 0;
|
|
right: 0;
|
|
top: 0;
|
|
height: 300rpx;
|
|
background: rgba(255, 255, 255, 0.72);
|
|
filter: blur(35rpx);
|
|
z-index: 0;
|
|
}
|
|
|
|
.detail-header {
|
|
position: relative;
|
|
z-index: 2;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: flex-start;
|
|
margin-bottom: 30rpx;
|
|
}
|
|
|
|
.header-left,
|
|
.header-right {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.header-title {
|
|
font-size: 34rpx;
|
|
font-weight: 600;
|
|
color: #1b1b1b;
|
|
}
|
|
|
|
.header-icon-gap {
|
|
margin-left: 18rpx;
|
|
}
|
|
|
|
.detail-scroll {
|
|
position: relative;
|
|
z-index: 2;
|
|
max-height: calc(100vh - 260rpx);
|
|
padding-bottom: 220rpx;
|
|
}
|
|
|
|
.detail-info {
|
|
padding: 20rpx 16rpx;
|
|
}
|
|
|
|
.detail-label {
|
|
display: inline-flex;
|
|
padding: 8rpx 22rpx;
|
|
border-radius: 40rpx;
|
|
font-size: 24rpx;
|
|
color: #4a5568;
|
|
background: rgba(255, 255, 255, 0.8);
|
|
margin-bottom: 16rpx;
|
|
}
|
|
|
|
.detail-title {
|
|
font-size: 40rpx;
|
|
font-weight: 600;
|
|
color: #16171d;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.detail-subtitle {
|
|
font-size: 26rpx;
|
|
color: #8b8f99;
|
|
margin-top: 16rpx;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.detail-author {
|
|
margin-top: 22rpx;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-end;
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.6);
|
|
padding-bottom: 20rpx;
|
|
}
|
|
|
|
.author-meta {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6rpx;
|
|
}
|
|
|
|
.author-name {
|
|
font-size: 28rpx;
|
|
color: #2c2c2c;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.author-desc {
|
|
font-size: 24rpx;
|
|
color: #a0a3b0;
|
|
}
|
|
|
|
.detail-date {
|
|
font-size: 24rpx;
|
|
color: #babec6;
|
|
}
|
|
|
|
.detail-cover {
|
|
//width: 100%;
|
|
height: 360rpx;
|
|
border-radius: 30rpx;
|
|
margin: 30rpx;
|
|
box-shadow: 0 20rpx 40rpx rgba(128, 139, 255, 0.2);
|
|
}
|
|
|
|
.detail-article {
|
|
margin: 30rpx;
|
|
background: rgba(255, 255, 255, 0.9);
|
|
border-radius: 32rpx;
|
|
padding: 30rpx;
|
|
box-shadow: 0 20rpx 40rpx rgba(128, 139, 255, 0.15);
|
|
}
|
|
|
|
.detail-paragraph {
|
|
font-size: 26rpx;
|
|
color: #555c6b;
|
|
line-height: 1.8;
|
|
margin-bottom: 26rpx;
|
|
}
|
|
|
|
.detail-paragraph:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.detail-action {
|
|
z-index: 999;
|
|
position: fixed;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
padding: 26rpx 40rpx calc(26rpx + env(safe-area-inset-bottom));
|
|
background: #ffffff;
|
|
border-top: 1px solid rgba(148, 163, 184, 0.2);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
box-shadow: 0 -12rpx 40rpx rgba(99, 102, 241, 0.12);
|
|
}
|
|
|
|
.action-item {
|
|
display: flex;
|
|
flex: 1;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 8rpx;
|
|
color: #1b1b1b;
|
|
font-size: 24rpx;
|
|
}
|
|
|
|
.action-text {
|
|
color: #1f2a37;
|
|
}
|
|
|
|
.action-button {
|
|
flex: 1;
|
|
margin-left: 40rpx;
|
|
height: 90rpx;
|
|
border-radius: 50rpx;
|
|
background: linear-gradient(120deg, #0b1c56, #1a2e8e);
|
|
color: #fff;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 12rpx;
|
|
font-size: 28rpx;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.action-btn-text {
|
|
color: #fff;
|
|
}
|
|
|
|
.comment-popup {
|
|
padding: 30rpx;
|
|
max-height: 70vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.comment-popup-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 20rpx;
|
|
}
|
|
|
|
.comment-popup-title {
|
|
font-size: 32rpx;
|
|
font-weight: 600;
|
|
color: #1f2933;
|
|
}
|
|
|
|
.comment-list {
|
|
flex: 1;
|
|
max-height: 60vh;
|
|
}
|
|
|
|
.comment-item {
|
|
padding: 20rpx 0;
|
|
border-bottom: 1px solid #eef2ff;
|
|
}
|
|
|
|
.comment-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.comment-item-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.comment-user {
|
|
font-size: 28rpx;
|
|
color: #111827;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.comment-time {
|
|
font-size: 22rpx;
|
|
color: #9ca3af;
|
|
}
|
|
|
|
.comment-content {
|
|
margin-top: 12rpx;
|
|
font-size: 26rpx;
|
|
line-height: 1.6;
|
|
color: #4b5563;
|
|
white-space: pre-wrap;
|
|
word-break: break-all;
|
|
}
|
|
|
|
.comment-placeholder {
|
|
text-align: center;
|
|
color: #9ca3af;
|
|
padding: 80rpx 0;
|
|
font-size: 26rpx;
|
|
}
|
|
|
|
.comment-footer {
|
|
padding-top: 24rpx;
|
|
}
|
|
|
|
.comment-publish-btn {
|
|
width: 100%;
|
|
text-align: center;
|
|
padding: 22rpx 0;
|
|
border-radius: 50rpx;
|
|
color: #fff;
|
|
font-size: 28rpx;
|
|
font-weight: 500;
|
|
background: linear-gradient(120deg, #0b1c56, #1a2e8e);
|
|
}
|
|
|
|
.publish-popup {
|
|
width: 640rpx;
|
|
padding: 40rpx 32rpx 32rpx;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.publish-header {
|
|
font-size: 32rpx;
|
|
font-weight: 600;
|
|
color: #111827;
|
|
margin-bottom: 24rpx;
|
|
}
|
|
|
|
.publish-textarea {
|
|
width: 100%;
|
|
min-height: 200rpx;
|
|
background: #f3f4f6;
|
|
border-radius: 24rpx;
|
|
padding: 24rpx;
|
|
font-size: 26rpx;
|
|
color: #1f2937;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.publish-actions {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 24rpx;
|
|
margin-top: 24rpx;
|
|
}
|
|
|
|
.publish-cancel {
|
|
padding: 16rpx 32rpx;
|
|
border-radius: 999rpx;
|
|
font-size: 26rpx;
|
|
color: #6b7280;
|
|
background: #f5f5f5;
|
|
}
|
|
|
|
.publish-submit {
|
|
padding: 16rpx 40rpx;
|
|
border-radius: 999rpx;
|
|
font-size: 26rpx;
|
|
color: #fff;
|
|
background: linear-gradient(120deg, #0b1c56, #1a2e8e);
|
|
}
|
|
|
|
.publish-submit.is-disabled {
|
|
opacity: 0.6;
|
|
}
|
|
</style>
|