You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

661 lines
15 KiB

<template>
<view class="detail-page">
<view class="nav" :style="{ paddingTop: statusBarTop + 'px' }">
<view class="nav-btn" @tap="back"><uni-icons type="left" size="24" color="#18372f"></uni-icons></view>
<view class="nav-title">闲置详情</view>
<view class="nav-btn" @tap="share"><uni-icons type="redo" size="22" color="#18372f"></uni-icons></view>
</view>
<scroll-view scroll-y class="content-scroll" :style="{ paddingTop: navHeight + 'px' }">
<swiper
class="image-swiper"
circular
indicator-dots
:style="{ height: currentImageHeight + 'rpx' }"
@change="changeImage"
>
<swiper-item v-for="(item,index) in imageList" :key="index">
<image
:src="item"
mode="aspectFit"
:data-index="index"
@load="onImageLoad"
></image>
</swiper-item>
</swiper>
<view class="card goods-card" v-if="goods">
<view class="price-row">
<view class="price">¥{{goods.price}}</view>
<view class="origin" v-if="goods.originPrice">¥{{goods.originPrice}}</view>
<view class="sale-status">{{saleStatusText}}</view>
</view>
<view class="title">{{goods.title}}</view>
<view class="meta">
<text>{{goods.createTime || ''}}</text>
<text>{{goods.browseCount || 0}}浏览</text>
<text>{{goods.regionName || '本校'}}</text>
</view>
<view class="tag-list" v-if="tagList.length">
<text v-for="item in tagList" :key="item">{{item}}</text>
</view>
<rich-text class="content rich-content" :nodes="goods.content || ''"></rich-text>
</view>
<view class="card seller-card" v-if="goods">
<image class="avatar" :src="goods.userAvatar || defaultAvatar" mode="aspectFill"></image>
<view class="seller-main">
<view class="seller-name">{{goods.userName || '校园同学'}} <text>已实名</text></view>
<view class="online">{{goods.sellerOnlineStatus || '3天内在线'}}</view>
</view>
<view class="seller-tip">线下面交</view>
</view>
<view class="card contact-card" v-if="hasContactInfo">
<view class="section-head">
<text>联系方式</text>
</view>
<view class="contact-row" v-if="contactInfo.wechat" @tap="copyText(contactInfo.wechat, '微信号已复制')">
<view class="contact-label">微信</view>
<view class="contact-value">{{contactInfo.wechat}}</view>
<view class="contact-action">复制</view>
</view>
<view class="contact-row" v-if="contactInfo.phone" @tap="makeCall(contactInfo.phone)">
<view class="contact-label">电话</view>
<view class="contact-value">{{contactInfo.phone}}</view>
<view class="contact-action">拨打</view>
</view>
</view>
<view class="card comment-card">
<view class="section-head">
<text>评论 {{commentsTotal}}</text>
<view @tap="focusComment">我要留言</view>
</view>
<view class="comment-item" v-for="item in comments" :key="item.id">
<image :src="item.userAvatar || defaultAvatar" mode="aspectFill"></image>
<view class="comment-main">
<view class="comment-name">{{item.userName || '校园同学'}}</view>
<view class="comment-content">{{item.content}}</view>
<view class="comment-actions">
<text>{{item.createTime || ''}}</text>
<text @tap="likeComment(item)">赞 {{item.likeCount || 0}}</text>
<text @tap="reportComment(item)">举报</text>
<text v-if="item.userId == currentUserId" @tap="deleteComment(item)">删除</text>
</view>
</view>
</view>
<view class="empty" v-if="!comments.length">还没有留言,快来问问吧</view>
</view>
<view class="bottom-space"></view>
</scroll-view>
<view class="comment-input" v-if="showCommentInput">
<input v-model.trim="commentText" :focus="showCommentInput" placeholder="写下你的留言" confirm-type="send" @confirm="submitComment" />
<view @tap="submitComment">发送</view>
</view>
<view class="bottom-bar">
<view class="bar-action" @tap="focusComment">
<uni-icons type="chat" size="22" color="#59706a"></uni-icons>
<text>留言</text>
</view>
<view class="bar-action" @tap="favorite">
<uni-icons :type="goods && goods.favorited ? 'star-filled' : 'star'" size="22" :color="goods && goods.favorited ? '#ffb22d' : '#59706a'"></uni-icons>
<text>收藏</text>
</view>
<view class="bar-action" @tap="reportGoods">
<uni-icons type="flag" size="22" color="#59706a"></uni-icons>
<text>举报</text>
</view>
<view class="consult-btn" @tap="consult">立即咨询</view>
</view>
<ie-auth-dialog />
</view>
</template>
<script>
import {
getFishGoodsDetail,
toggleFishFavorite,
toggleFishLike,
addFishComment,
pageFishComments,
deleteFishComment,
submitFishComplaint,
createFishChatSession
} from '@/common/fishMarketApi.js'
import { getIeProfile } from '@/common/ieApi.js'
import { ensureIeVerifiedBeforeAction } from '@/common/ieRealNameAuth.js'
import IeAuthDialog from '@/package2/components/ie-auth-dialog/ie-auth-dialog.vue'
export default {
components: {
IeAuthDialog
},
data() {
return {
id: '',
statusBarTop: 24,
goods: null,
comments: [],
commentsTotal: 0,
commentText: '',
showCommentInput: false,
defaultAvatar: 'https://jewel-shop.oss-cn-beijing.aliyuncs.com/801c569079da4540990c1cc634186fdd.png',
currentUserId: uni.getStorageSync('id') || '',
currentImageIndex: 0,
imageHeights: {}
}
},
computed: {
imageList() {
return this.goods && this.goods.imageList && this.goods.imageList.length ? this.goods.imageList.slice(0, 5) : [this.defaultAvatar]
},
tagList() {
return this.goods && this.goods.tagList ? this.goods.tagList : []
},
saleStatusText() {
const status = this.goods ? Number(this.goods.saleStatus) : 1
if (status == 2) return '已预定'
if (status == 3) return '已卖出'
return '在售'
},
contactInfo() {
if (!this.goods) return {}
const value = this.goods.contactInfo
let info = {}
if (typeof value === 'string') {
try {
info = JSON.parse(value) || {}
} catch (e) {
info = {}
}
} else if (value && typeof value === 'object') {
info = value
}
return {
wechat: info.wechat || this.goods.wechat || '',
phone: info.phone || this.goods.phone || ''
}
},
hasContactInfo() {
return !!(this.contactInfo.wechat || this.contactInfo.phone)
},
navHeight() {
return this.statusBarTop + uni.upx2px(68)
},
currentImageHeight() {
return this.imageHeights[this.currentImageIndex] || 560
}
},
onLoad(options) {
this.id = options && options.id ? options.id : ''
if (uni.getMenuButtonBoundingClientRect) {
this.statusBarTop = uni.getMenuButtonBoundingClientRect().top || this.statusBarTop
}
this.load()
},
methods: {
back() {
uni.navigateBack()
},
load() {
if (!this.id) return
getFishGoodsDetail(this.id).then(res => {
this.goods = res
this.currentImageIndex = 0
this.imageHeights = {}
})
this.loadComments()
},
changeImage(e) {
this.currentImageIndex = Number(e.detail && e.detail.current) || 0
},
onImageLoad(e) {
const detail = e && e.detail ? e.detail : {}
const dataset = e && e.currentTarget ? e.currentTarget.dataset || {} : {}
const index = Number(dataset.index) || 0
const width = Number(detail.width)
const height = Number(detail.height)
if (!width || !height) return
const adaptedHeight = Math.max(360, Math.min(900, Math.round(750 * height / width)))
this.$set(this.imageHeights, index, adaptedHeight)
},
loadComments() {
pageFishComments(this.id, 1, 20).then(page => {
this.comments = page && (page.records || page.content) ? (page.records || page.content) : []
this.commentsTotal = Number(page && (page.total || page.totalElements)) || this.comments.length
})
},
async ensureVerified(actionText) {
const profile = await getIeProfile().catch(() => null)
return ensureIeVerifiedBeforeAction({
profile: profile || {},
actionText,
authDialog: {
badge: '校园二手',
title: '实名认证',
content: '根据法律法规要求,对应流程需要实名认证。',
steps: []
},
reload: getIeProfile
})
},
async focusComment() {
const verified = await this.ensureVerified('在校园二手留言')
if (!verified) return
this.showCommentInput = true
},
async submitComment() {
if (!this.commentText) return
addFishComment({ goodsId: this.id, content: this.commentText }).then(res => {
if (!res) return
this.commentText = ''
this.showCommentInput = false
this.loadComments()
})
},
deleteComment(item) {
uni.showModal({
title: '删除评论',
content: '确定删除这条评论吗?',
success: (res) => {
if (!res.confirm) return
deleteFishComment(item.id).then(() => this.loadComments())
}
})
},
likeComment(item) {
toggleFishLike({ commentId: item.id, likeType: 'comment' }).then(res => {
if (!res) return
item.likeCount = Number(res.count) || 0
})
},
favorite() {
if (!this.goods) return
toggleFishFavorite(this.id).then(res => {
if (!res) return
this.$set(this.goods, 'favorited', !!res.favorited)
this.$set(this.goods, 'favoriteCount', Number(res.count) || 0)
})
},
reportGoods() {
this.report({ complaintType: 'goods', goodsId: this.id })
},
reportComment(item) {
this.report({ complaintType: 'comment', goodsId: this.id, commentId: item.id })
},
report(base) {
uni.showModal({
title: '举报原因',
editable: true,
placeholderText: '请描述问题',
success: (res) => {
if (!res.confirm || !res.content) return
submitFishComplaint({ ...base, reason: res.content }).then(() => {
uni.showToast({ title: '已提交举报', icon: 'none' })
})
}
})
},
async consult() {
if (!this.goods) return
const verified = await this.ensureVerified('咨询校园二手商品')
if (!verified) return
createFishChatSession(this.id).then(session => {
if (!session || !session.sessionId) return
uni.navigateTo({
url: `/package2/IdleTrad/chat?sessionId=${session.sessionId}&goodsId=${this.id}`
})
})
},
share() {
uni.showToast({ title: '请使用右上角分享', icon: 'none' })
},
copyText(text, title) {
if (!text) return
uni.setClipboardData({
data: String(text),
success: () => {
uni.showToast({ title: title || '已复制', icon: 'none' })
}
})
},
makeCall(phone) {
if (!phone) return
uni.makePhoneCall({
phoneNumber: String(phone)
})
}
}
}
</script>
<style lang="scss">
page {
background: #f7f9f7;
}
.detail-page {
min-height: 100vh;
color: #17362f;
background: #f7f9f7;
}
.nav {
position: fixed;
left: 0;
right: 0;
top: 0;
z-index: 20;
padding-left: 24rpx;
padding-right: 24rpx;
display: flex;
align-items: center;
background: rgba(255, 255, 255, 0.94);
box-shadow: 0 8rpx 20rpx rgba(20, 68, 52, 0.06);
}
.nav-btn {
width: 68rpx;
height: 68rpx;
display: flex;
align-items: center;
}
.nav-title {
flex: 1;
text-align: center;
font-size: 30rpx;
font-weight: 900;
}
.content-scroll {
height: 100vh;
box-sizing: border-box;
}
.image-swiper {
width: 100%;
background: #edf3ef;
transition: height 0.2s ease;
}
.image-swiper image {
width: 100%;
height: 100%;
display: block;
}
.card {
margin: 20rpx 24rpx 0;
padding: 24rpx;
border-radius: 28rpx;
background: #fff;
box-shadow: 0 10rpx 26rpx rgba(20, 60, 48, 0.05);
}
.price-row {
display: flex;
align-items: flex-end;
}
.price {
color: #ef4c3f;
font-size: 44rpx;
font-weight: 900;
}
.origin {
margin-left: 14rpx;
color: #aab3af;
font-size: 24rpx;
text-decoration: line-through;
}
.sale-status {
margin-left: auto;
padding: 8rpx 16rpx;
border-radius: 22rpx;
background: #e8fbf3;
color: #1aa376;
font-size: 22rpx;
font-weight: 800;
}
.title {
margin-top: 16rpx;
font-size: 34rpx;
font-weight: 900;
line-height: 48rpx;
}
.meta {
margin-top: 14rpx;
display: flex;
gap: 22rpx;
color: #8a9692;
font-size: 22rpx;
}
.tag-list {
margin-top: 18rpx;
display: flex;
flex-wrap: wrap;
gap: 12rpx;
}
.tag-list text {
padding: 6rpx 14rpx;
border-radius: 16rpx;
background: #f0faf6;
color: #23a77b;
font-size: 22rpx;
}
.content {
margin-top: 22rpx;
color: #31443e;
font-size: 27rpx;
line-height: 44rpx;
}
.rich-content {
display: block;
word-break: break-all;
}
.rich-content ::v-deep img,
.rich-content ::v-deep image {
max-width: 100%;
border-radius: 16rpx;
}
.seller-card {
display: flex;
align-items: center;
}
.avatar,
.comment-item image {
width: 82rpx;
height: 82rpx;
border-radius: 50%;
background: #edf3ef;
}
.seller-main {
flex: 1;
margin-left: 18rpx;
}
.seller-name {
font-size: 28rpx;
font-weight: 900;
}
.seller-name text {
margin-left: 8rpx;
color: #1aa376;
font-size: 20rpx;
}
.online,
.seller-tip {
margin-top: 8rpx;
color: #8a9692;
font-size: 22rpx;
}
.contact-card {
padding-bottom: 12rpx;
}
.contact-row {
min-height: 78rpx;
margin-top: 18rpx;
padding: 0 20rpx;
display: flex;
align-items: center;
border-radius: 18rpx;
background: #f4f8f6;
}
.contact-label {
width: 88rpx;
color: #73827d;
font-size: 24rpx;
}
.contact-value {
flex: 1;
min-width: 0;
color: #223b35;
font-size: 27rpx;
font-weight: 800;
word-break: break-all;
}
.contact-action {
margin-left: 16rpx;
color: #1aa376;
font-size: 24rpx;
font-weight: 900;
}
.section-head {
display: flex;
justify-content: space-between;
font-size: 30rpx;
font-weight: 900;
}
.section-head view {
color: #22aa7d;
font-size: 24rpx;
}
.comment-item {
margin-top: 24rpx;
display: flex;
}
.comment-main {
flex: 1;
margin-left: 16rpx;
}
.comment-name {
color: #72827d;
font-size: 23rpx;
}
.comment-content {
margin-top: 8rpx;
font-size: 26rpx;
line-height: 38rpx;
}
.comment-actions {
margin-top: 10rpx;
display: flex;
gap: 18rpx;
color: #9aa8a4;
font-size: 21rpx;
}
.empty {
padding: 50rpx 0;
color: #9aa8a4;
text-align: center;
}
.comment-input {
position: fixed;
left: 0;
right: 0;
bottom: 126rpx;
z-index: 30;
padding: 16rpx 24rpx;
display: flex;
background: #fff;
box-shadow: 0 -8rpx 20rpx rgba(0, 0, 0, 0.04);
}
.comment-input input {
flex: 1;
height: 70rpx;
padding: 0 20rpx;
border-radius: 36rpx;
background: #f3f7f5;
}
.comment-input view {
width: 100rpx;
color: #1aa376;
text-align: center;
line-height: 70rpx;
font-weight: 800;
}
.bottom-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 20;
padding: 14rpx 24rpx calc(14rpx + env(safe-area-inset-bottom));
display: flex;
align-items: center;
background: #fff;
box-shadow: 0 -8rpx 22rpx rgba(20, 60, 48, 0.06);
}
.bar-action {
width: 92rpx;
display: flex;
flex-direction: column;
align-items: center;
color: #59706a;
font-size: 20rpx;
}
.consult-btn {
flex: 1;
height: 82rpx;
margin-left: 20rpx;
border-radius: 44rpx;
background: linear-gradient(135deg, #37c990, #19af7e);
color: #fff;
font-size: 30rpx;
font-weight: 900;
text-align: center;
line-height: 82rpx;
}
.bottom-space {
height: 170rpx;
}
</style>