wangfukang 1 week ago
parent
commit
b2b8544e47
  1. 48
      package2/IdleTrad/chat.vue
  2. 324
      package2/IdleTrad/detail.vue
  3. 165
      package2/IdleTrad/sessionList.vue
  4. 582
      package2/IdleTrad/wantList.vue
  5. 190
      package2/myCenter/myPost.vue

48
package2/IdleTrad/chat.vue

@ -9,10 +9,11 @@
</view>
<view class="goods-card" v-if="goods" @tap="goGoods">
<image class="goods-cover" :src="goods.coverImage || imageList[0] || defaultAvatar" mode="aspectFill"></image>
<image class="goods-cover" v-if="goodsCover" :src="goodsCover" mode="aspectFill"></image>
<view class="goods-cover no-cover" v-else>求购</view>
<view class="goods-main">
<view class="goods-title">{{goods.title}}</view>
<view class="goods-price">¥{{goods.price}}</view>
<view class="goods-price">{{isWant ? '预算 ¥' : '¥'}}{{displayPrice}}</view>
</view>
<view class="goods-link">查看 </view>
</view>
@ -64,6 +65,7 @@
import fishMarketSocket from '@/common/fishMarketSocket.js'
import {
getFishGoodsDetail,
getFishWantDetail,
getFishChatSession,
pageFishChatMessages,
uploadFishImage
@ -75,6 +77,7 @@
statusBarTop: 24,
sessionId: '',
goodsId: '',
wantId: '',
session: null,
goods: null,
messages: [],
@ -95,8 +98,22 @@
if (!this.goods || !this.goods.imageList) return []
return this.goods.imageList
},
isWant() {
return !!this.wantId
},
goodsCover() {
if (!this.goods) return ''
return this.goods.coverImage || this.imageList[0] || ''
},
displayPrice() {
if (!this.goods) return 0
return this.isWant ? (this.goods.expectedPrice || 0) : (this.goods.price || 0)
},
peerName() {
if (!this.goods || !this.session) return '校园同学'
if (this.isWant) {
return this.selfId == String(this.session.buyerId) ? '供货同学' : (this.goods.userName || '求购同学')
}
return this.selfId == String(this.session.sellerId) ? '买家同学' : (this.goods.userName || '卖家同学')
},
peerAvatar() {
@ -106,6 +123,7 @@
onLoad(options) {
this.sessionId = options && options.sessionId ? options.sessionId : ''
this.goodsId = options && options.goodsId ? options.goodsId : ''
this.wantId = options && options.wantId ? options.wantId : ''
if (uni.getMenuButtonBoundingClientRect) {
this.statusBarTop = uni.getMenuButtonBoundingClientRect().top || this.statusBarTop
}
@ -127,11 +145,17 @@
getFishChatSession(this.sessionId).then(session => {
this.session = session
const goodsId = this.goodsId || (session && session.goodsId)
const wantId = this.wantId || (session && session.wantId)
if (goodsId) {
this.goodsId = goodsId
getFishGoodsDetail(goodsId).then(goods => {
this.goods = goods
})
} else if (wantId) {
this.wantId = wantId
getFishWantDetail(wantId).then(want => {
this.goods = want
})
}
})
this.loadMessages(true).then(() => {
@ -149,6 +173,9 @@
this.totalPages = Number(page && (page.pages || page.totalPages)) || 1
this.messages = reset ? normalized : normalized.concat(this.messages)
this.scrollToBottom()
if (reset) {
uni.$emit('fishUnreadChange')
}
})
},
loadOlder() {
@ -272,11 +299,17 @@
uni.previewImage({ urls: [url], current: url })
},
goGoods() {
if (!this.goodsId) return
if (this.isWant) {
if (!this.wantId) return
uni.navigateTo({ url: '/package2/IdleTrad/detail?type=want&id=' + this.wantId })
return
}
if (this.goodsId) {
uni.navigateTo({ url: '/package2/IdleTrad/detail?id=' + this.goodsId })
}
}
}
}
</script>
<style lang="scss">
@ -343,6 +376,15 @@
background: #edf4f1;
}
.no-cover {
display: flex;
align-items: center;
justify-content: center;
color: #7f8d88;
font-size: 22rpx;
font-weight: 900;
}
.goods-main {
flex: 1;
min-width: 0;

324
package2/IdleTrad/detail.vue

@ -2,12 +2,14 @@
<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-title">{{isWant ? '求购详情' : '闲置详情'}}</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' }">
<view class="detail-loading" v-if="loading">加载中...</view>
<swiper
v-if="goods && imageList.length"
class="image-swiper"
circular
indicator-dots
@ -26,8 +28,8 @@
<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="price">{{isWant ? '预算 ¥' : '¥'}}{{displayPrice}}</view>
<view class="origin" v-if="!isWant && goods.originPrice">¥{{goods.originPrice}}</view>
<view class="sale-status">{{saleStatusText}}</view>
</view>
<view class="title">{{goods.title}}</view>
@ -36,7 +38,8 @@
<text>{{goods.browseCount || 0}}浏览</text>
<text>{{goods.regionName || '本校'}}</text>
</view>
<view class="tag-list" v-if="tagList.length">
<view class="tag-list" v-if="tagList.length || isWant">
<text v-if="isWant">求购</text>
<text v-for="item in tagList" :key="item">{{item}}</text>
</view>
<rich-text class="content rich-content" :nodes="goods.content || ''"></rich-text>
@ -45,7 +48,7 @@
<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="seller-name">半径卖家<text>已实名</text></view>
<view class="online">{{goods.sellerOnlineStatus || '3天内在线'}}</view>
</view>
<view class="seller-tip">线下面交</view>
@ -67,41 +70,72 @@
</view>
</view>
<view class="card comment-card">
<view class="card comment-card" v-if="goods && !isWant">
<view class="section-head">
<text>评论 {{commentsTotal}}</text>
<view @tap="focusComment">我要留言</view>
<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-name">
{{item.userName || '校园同学'}}
<text v-if="item.sellerReply">卖家</text>
</view>
<view class="comment-content">{{item.content}}</view>
<view class="reply-list" v-if="item.replies && item.replies.length">
<view class="reply-item" v-for="reply in item.replies" :key="reply.id">
<text class="reply-name">{{reply.userName || '校园同学'}}</text>
<text class="seller-badge" v-if="reply.sellerReply">卖家</text>
<text class="reply-target" v-if="reply.replyToUserName"> 回复 {{reply.replyToUserName}}</text>
<text>{{reply.content}}</text>
<text class="reply-action" @tap="replyComment(item, reply)">回复</text>
</view>
</view>
<view class="comment-actions">
<text>{{item.createTime || ''}}</text>
<text @tap="likeComment(item)"> {{item.likeCount || 0}}</text>
<text @tap="replyComment(item, item)">回复</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 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>
<uni-popup ref="commentPopup" type="center" background-color="transparent" @change="onCommentPopupChange">
<view class="comment-popup">
<view class="comment-popup-title">{{replyTarget.id ? '回复评论' : '发表评论'}}</view>
<view class="comment-popup-desc" v-if="replyTarget.id">
回复 {{replyTarget.userName || '校园同学'}}
<text @tap="cancelReply">取消回复</text>
</view>
<view class="comment-popup-desc" v-else>友善交流评论内容会进行审核</view>
<view class="comment-popup-input">
<textarea
v-model.trim="commentText"
:placeholder="commentPlaceholder"
maxlength="200"
cursor-spacing="120"
/>
</view>
<view class="comment-popup-actions">
<view class="comment-popup-cancel" @tap="closeCommentPopup">取消</view>
<view class="comment-popup-confirm" :class="{ disabled: commentSubmitting }" @tap="submitComment">{{commentSubmitting ? '发送中' : '发送'}}</view>
</view>
</view>
</uni-popup>
<view class="bottom-bar">
<view class="bar-action" @tap="focusComment">
<view class="bottom-bar" v-if="goods">
<view class="bar-action" v-if="!isWant" @tap="focusComment">
<uni-icons type="chat" size="22" color="#59706a"></uni-icons>
<text>留言</text>
<text>评论</text>
</view>
<view class="bar-action" @tap="favorite">
<view class="bar-action" v-if="!isWant" @tap="favorite">
<uni-icons :type="goods && goods.favorited ? 'star-filled' : 'star'" size="22" :color="goods && goods.favorited ? '#ffb22d' : '#59706a'"></uni-icons>
<text>收藏</text>
</view>
@ -109,7 +143,7 @@
<uni-icons type="flag" size="22" color="#59706a"></uni-icons>
<text>举报</text>
</view>
<view class="consult-btn" @tap="consult">立即咨询</view>
<view class="consult-btn" @tap="consult">{{isWant ? '我有货' : '立即咨询'}}</view>
</view>
<ie-auth-dialog />
</view>
@ -118,13 +152,15 @@
<script>
import {
getFishGoodsDetail,
getFishWantDetail,
toggleFishFavorite,
toggleFishLike,
addFishComment,
pageFishComments,
deleteFishComment,
submitFishComplaint,
createFishChatSession
createFishChatSession,
createFishWantChatSession
} from '@/common/fishMarketApi.js'
import { getIeProfile } from '@/common/ieApi.js'
import { ensureIeVerifiedBeforeAction } from '@/common/ieRealNameAuth.js'
@ -137,13 +173,17 @@
data() {
return {
id: '',
detailType: 'goods',
statusBarTop: 24,
goods: null,
loading: false,
comments: [],
commentsTotal: 0,
commentText: '',
showCommentInput: false,
defaultAvatar: 'https://jewel-shop.oss-cn-beijing.aliyuncs.com/801c569079da4540990c1cc634186fdd.png',
commentSubmitting: false,
replyTarget: {},
defaultAvatar: '',
currentUserId: uni.getStorageSync('id') || '',
currentImageIndex: 0,
imageHeights: {}
@ -151,13 +191,20 @@
},
computed: {
imageList() {
return this.goods && this.goods.imageList && this.goods.imageList.length ? this.goods.imageList.slice(0, 5) : [this.defaultAvatar]
return this.goods && this.goods.imageList && this.goods.imageList.length ? this.goods.imageList.slice(0, 5) : []
},
isWant() {
return this.detailType == 'want'
},
displayPrice() {
return this.isWant ? (this.goods && this.goods.expectedPrice || 0) : (this.goods && this.goods.price || 0)
},
tagList() {
return this.goods && this.goods.tagList ? this.goods.tagList : []
},
saleStatusText() {
const status = this.goods ? Number(this.goods.saleStatus) : 1
if (this.isWant) return '求购中'
if (status == 2) return '已预定'
if (status == 3) return '已卖出'
return '在售'
@ -188,10 +235,14 @@
},
currentImageHeight() {
return this.imageHeights[this.currentImageIndex] || 560
},
commentPlaceholder() {
return this.replyTarget && this.replyTarget.id ? '回复 ' + (this.replyTarget.userName || '校园同学') : '写下你的评论'
}
},
onLoad(options) {
this.id = options && options.id ? options.id : ''
this.detailType = options && options.type == 'want' ? 'want' : 'goods'
if (uni.getMenuButtonBoundingClientRect) {
this.statusBarTop = uni.getMenuButtonBoundingClientRect().top || this.statusBarTop
}
@ -203,12 +254,16 @@
},
load() {
if (!this.id) return
getFishGoodsDetail(this.id).then(res => {
this.loading = true
const request = this.isWant ? getFishWantDetail(this.id) : getFishGoodsDetail(this.id)
request.then(res => {
this.goods = res
this.currentImageIndex = 0
this.imageHeights = {}
}).finally(() => {
this.loading = false
})
this.loadComments()
if (!this.isWant) this.loadComments()
},
changeImage(e) {
this.currentImageIndex = Number(e.detail && e.detail.current) || 0
@ -227,6 +282,7 @@
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
uni.$emit('fishUnreadChange')
})
},
async ensureVerified(actionText) {
@ -237,24 +293,81 @@
authDialog: {
badge: '校园二手',
title: '实名认证',
content: '根据法律法规要求,对应流程需要实名认证。',
content: '根据法律法规要求,对应流程需要调用阿里云实名认证。半径里不会存储您的实名信息。',
steps: []
},
reload: getIeProfile
})
},
async focusComment() {
const verified = await this.ensureVerified('在校园二手留言')
if (this.isWant) return
const verified = await this.ensureVerified('在校园二手评论')
if (!verified) return
this.replyTarget = {}
this.openCommentPopup()
},
async replyComment(parent, target) {
if (this.isWant) return
const verified = await this.ensureVerified('回复校园二手评论')
if (!verified) return
this.replyTarget = {
id: target.id,
parentId: parent.parentId || parent.id,
userId: target.userId,
userName: target.userName || '校园同学'
}
this.openCommentPopup()
},
cancelReply() {
this.replyTarget = {}
},
openCommentPopup() {
this.showCommentInput = true
this.$refs.commentPopup.open('center')
},
closeCommentPopup() {
this.showCommentInput = false
uni.hideKeyboard()
this.$refs.commentPopup.close()
},
onCommentPopupChange(e) {
if (!e.show) {
this.showCommentInput = false
uni.hideKeyboard()
}
},
async submitComment() {
if (!this.commentText) return
addFishComment({ goodsId: this.id, content: this.commentText }).then(res => {
if (!res) return
if (this.commentSubmitting) return
const content = String(this.commentText || '').trim()
if (!content) return
const replyTarget = { ...(this.replyTarget || {}) }
const payload = {
goodsId: this.id,
content
}
if (replyTarget && replyTarget.id) {
payload.parentId = replyTarget.parentId
payload.replyToUserId = replyTarget.userId
payload.replyToUserName = replyTarget.userName
}
this.commentSubmitting = true
this.closeCommentPopup()
addFishComment(payload).then(res => {
if (!res) {
this.commentText = content
this.replyTarget = replyTarget
this.openCommentPopup()
return
}
this.commentText = ''
this.showCommentInput = false
this.replyTarget = {}
this.loadComments()
}).catch(() => {
this.commentText = content
this.replyTarget = replyTarget
this.openCommentPopup()
}).finally(() => {
this.commentSubmitting = false
})
},
deleteComment(item) {
@ -282,7 +395,7 @@
})
},
reportGoods() {
this.report({ complaintType: 'goods', goodsId: this.id })
this.report({ complaintType: this.isWant ? 'want' : 'goods', goodsId: this.id })
},
reportComment(item) {
this.report({ complaintType: 'comment', goodsId: this.id, commentId: item.id })
@ -302,8 +415,17 @@
},
async consult() {
if (!this.goods) return
const verified = await this.ensureVerified('咨询校园二手商品')
const verified = await this.ensureVerified(this.isWant ? '响应校园二手求购' : '咨询校园二手商品')
if (!verified) return
if (this.isWant) {
createFishWantChatSession(this.id).then(session => {
if (!session || !session.sessionId) return
uni.navigateTo({
url: `/package2/IdleTrad/chat?sessionId=${session.sessionId}&wantId=${this.id}`
})
})
return
}
createFishChatSession(this.id).then(session => {
if (!session || !session.sessionId) return
uni.navigateTo({
@ -377,6 +499,13 @@
box-sizing: border-box;
}
.detail-loading {
padding: 120rpx 0;
color: #8a9994;
font-size: 25rpx;
text-align: center;
}
.image-swiper {
width: 100%;
background: #edf3ef;
@ -572,12 +701,57 @@
font-size: 23rpx;
}
.comment-name text,
.seller-badge {
margin-left: 8rpx;
padding: 2rpx 8rpx;
border-radius: 10rpx;
background: #e8fbf3;
color: #1aa376;
font-size: 18rpx;
font-weight: 900;
}
.comment-content {
margin-top: 8rpx;
font-size: 26rpx;
line-height: 38rpx;
}
.reply-list {
margin-top: 12rpx;
padding: 14rpx 16rpx;
border-radius: 16rpx;
background: #f4f8f6;
}
.reply-item {
margin-top: 8rpx;
color: #40544e;
font-size: 23rpx;
line-height: 36rpx;
word-break: break-all;
}
.reply-item:first-child {
margin-top: 0;
}
.reply-name {
color: #1d8f70;
font-weight: 900;
}
.reply-target {
color: #7f8d88;
}
.reply-action {
margin-left: 14rpx;
color: #22aa7d;
font-weight: 900;
}
.comment-actions {
margin-top: 10rpx;
display: flex;
@ -592,32 +766,84 @@
text-align: center;
}
.comment-input {
position: fixed;
left: 0;
right: 0;
bottom: 126rpx;
z-index: 30;
padding: 16rpx 24rpx;
display: flex;
.comment-popup {
width: 620rpx;
padding: 44rpx 36rpx 34rpx;
box-sizing: border-box;
border-radius: 32rpx;
background: #fff;
box-shadow: 0 -8rpx 20rpx rgba(0, 0, 0, 0.04);
box-shadow: 0 28rpx 70rpx rgba(0, 35, 28, 0.16);
transition: transform 0.2s ease;
}
.comment-input input {
flex: 1;
height: 70rpx;
padding: 0 20rpx;
border-radius: 36rpx;
.comment-popup-title {
color: #17362f;
font-size: 34rpx;
font-weight: 900;
text-align: center;
}
.comment-popup-desc {
margin-top: 12rpx;
color: #7a8782;
font-size: 24rpx;
text-align: center;
}
.comment-popup-desc text {
margin-left: 16rpx;
color: #18a878;
font-weight: 900;
}
.comment-popup-input {
min-height: 170rpx;
margin-top: 32rpx;
padding: 20rpx 24rpx;
border-radius: 24rpx;
background: #f3f7f5;
box-sizing: border-box;
}
.comment-input view {
width: 100rpx;
color: #1aa376;
.comment-popup-input textarea {
width: 100%;
height: 150rpx;
min-height: 150rpx;
color: #253b35;
font-size: 28rpx;
line-height: 42rpx;
}
.comment-popup-actions {
display: flex;
margin-top: 34rpx;
gap: 18rpx;
}
.comment-popup-cancel,
.comment-popup-confirm {
flex: 1;
height: 76rpx;
border-radius: 999rpx;
font-size: 28rpx;
font-weight: 900;
line-height: 76rpx;
text-align: center;
line-height: 70rpx;
font-weight: 800;
}
.comment-popup-cancel {
background: #f3f6f4;
color: #777;
}
.comment-popup-confirm {
background: linear-gradient(135deg, #37c990, #19af7e);
color: #fff;
box-shadow: 0 12rpx 26rpx rgba(25, 175, 126, 0.18);
}
.comment-popup-confirm.disabled {
opacity: .62;
}
.bottom-bar {

165
package2/IdleTrad/sessionList.vue

@ -4,15 +4,26 @@
<view class="back" @tap="back"><uni-icons type="left" size="22" color="#15362f"></uni-icons></view>
<view class="title">我的咨询</view>
<view class="subtitle">我咨询的和咨询我的都在这里</view>
<view class="message-tabs">
<view class="message-tab" :class="{ active: activeTab == 'chat' }" @tap="switchTab('chat')">
<text>我的消息</text>
<view class="tab-badge" v-if="chatUnread > 0">{{formatBadgeCount(chatUnread)}}</view>
</view>
<view class="message-tab" :class="{ active: activeTab == 'comment' }" @tap="switchTab('comment')">
<text>我的留言</text>
<view class="tab-badge" v-if="commentUnread > 0">{{formatBadgeCount(commentUnread)}}</view>
</view>
</view>
</view>
<scroll-view scroll-y class="session-list" @scrolltolower="loadMore">
<view class="session-card" v-for="item in list" :key="item.id" @tap="openChat(item)">
<view class="session-card" v-for="item in list" :key="item.id" @tap="openItem(item)">
<image class="cover" :src="item.goodsCover || defaultCover" mode="aspectFill"></image>
<view class="main">
<view class="top-row">
<view class="goods-title">{{item.goodsTitle || '校园二手商品'}}</view>
<view class="role-tag" :class="{ seller: item.role == 'seller' }">{{item.roleText}}</view>
<view class="card-badge" v-if="item.unreadCount > 0">{{formatBadgeCount(item.unreadCount)}}</view>
</view>
<view class="goods-meta">
<text v-if="item.goodsPrice !== ''">¥{{item.goodsPrice}}</text>
@ -33,18 +44,23 @@
<script>
import {
pageMyFishSessions,
getFishGoodsDetail
pageMyFishMessageComments,
getFishUnreadSummary
} from '@/common/fishMarketApi.js'
export default {
data() {
return {
statusBarTop: 24,
activeTab: 'chat',
list: [],
pageNum: 1,
pageSize: 10,
totalPages: 1,
loading: false,
loadedOnce: false,
chatUnread: 0,
commentUnread: 0,
selfId: String(uni.getStorageSync('id') || ''),
defaultCover: '/static/images/img/songshu.png'
}
@ -55,10 +71,20 @@
}
this.load()
},
onShow() {
if (!this.loadedOnce) return
this.reload()
},
methods: {
back() {
uni.navigateBack()
},
reload() {
this.pageNum = 1
this.totalPages = 1
this.list = []
this.load()
},
loadMore() {
if (this.loading || this.pageNum >= this.totalPages) return
this.pageNum += 1
@ -67,45 +93,87 @@
load() {
if (this.loading) return
this.loading = true
pageMyFishSessions({
this.loadUnreadSummary()
const request = this.activeTab == 'comment' ? pageMyFishMessageComments : pageMyFishSessions
request({
pageNum: this.pageNum,
pageSize: this.pageSize
}).then(async page => {
}).then(page => {
const records = page && (page.records || page.content) ? (page.records || page.content) : []
this.totalPages = Number(page && (page.pages || page.totalPages)) || 1
const normalized = await this.normalizeSessions(records)
const normalized = this.activeTab == 'comment' ? this.normalizeComments(records) : this.normalizeSessions(records)
this.list = this.pageNum == 1 ? normalized : this.list.concat(normalized)
}).finally(() => {
this.loading = false
this.loadedOnce = true
})
},
async normalizeSessions(records) {
const result = []
for (let i = 0; i < records.length; i += 1) {
const session = records[i]
let goods = null
if (session.goodsId) {
goods = await getFishGoodsDetail(session.goodsId).catch(() => null)
}
switchTab(tab) {
if (this.activeTab == tab) return
this.activeTab = tab
this.pageNum = 1
this.totalPages = 1
this.list = []
this.load()
},
loadUnreadSummary() {
getFishUnreadSummary().then(res => {
this.chatUnread = Number(res && res.chatUnread) || 0
this.commentUnread = Number(res && res.commentUnread) || 0
uni.$emit('fishUnreadChange', {
chatUnread: this.chatUnread,
commentUnread: this.commentUnread,
fishUnread: this.chatUnread + this.commentUnread
})
}).catch(() => {})
},
formatBadgeCount(count) {
const value = Number(count) || 0
return value > 99 ? '99+' : value
},
normalizeSessions(records) {
return (Array.isArray(records) ? records : []).map(session => {
const isWant = session.bizType == 'want' || !!session.wantId
const role = String(session.sellerId) == this.selfId ? 'seller' : 'buyer'
const imageList = goods && goods.imageList ? goods.imageList : []
result.push({
return {
...session,
isWant,
role,
roleText: role == 'seller' ? '咨询我的' : '我咨询的',
goodsTitle: goods ? goods.title : '',
goodsCover: goods ? (goods.coverImage || imageList[0] || '') : '',
goodsPrice: goods && goods.price !== undefined ? goods.price : '',
desc: role == 'seller' ? '买家正在咨询这件商品' : '你正在咨询这件商品'
roleText: isWant ? (role == 'seller' ? '我供货的' : '我的求购') : (role == 'seller' ? '咨询我的' : '我咨询的'),
goodsTitle: session.displayTitle || '',
goodsCover: session.displayCover || '',
goodsPrice: session.displayPrice !== undefined && session.displayPrice !== null ? session.displayPrice : '',
desc: isWant ? (role == 'seller' ? '你正在响应这条求购' : '同学正在响应你的求购') : (role == 'seller' ? '买家正在咨询这件商品' : '你正在咨询这件商品')
}
})
},
normalizeComments(records) {
return (Array.isArray(records) ? records : []).map(comment => {
const isMine = String(comment.userId) == this.selfId
return {
...comment,
role: isMine ? 'buyer' : 'seller',
roleText: isMine ? '我留言的' : (comment.sellerReply ? '卖家回复' : '留言我的'),
goodsTitle: comment.displayTitle || '',
goodsCover: comment.displayCover || '',
goodsPrice: comment.displayPrice !== undefined && comment.displayPrice !== null ? comment.displayPrice : '',
desc: (comment.replyToUserName ? (comment.userName || '校园同学') + ' 回复 ' + comment.replyToUserName + ':' : '') + (comment.content || '')
}
})
},
openItem(item) {
if (this.activeTab == 'comment') {
if (!item.goodsId) return
uni.navigateTo({ url: '/package2/IdleTrad/detail?id=' + item.goodsId })
return
}
return result
this.openChat(item)
},
openChat(item) {
const sessionId = item.sessionId || item.id
if (!sessionId) return
uni.navigateTo({
url: `/package2/IdleTrad/chat?sessionId=${sessionId}&goodsId=${item.goodsId || ''}`
url: `/package2/IdleTrad/chat?sessionId=${sessionId}&goodsId=${item.targetGoodsId || item.goodsId || ''}&wantId=${item.targetWantId || item.wantId || ''}`
})
}
}
@ -146,8 +214,59 @@
font-size: 23rpx;
}
.message-tabs {
margin-top: 24rpx;
padding: 8rpx;
display: flex;
border-radius: 28rpx;
background: rgba(255, 255, 255, .82);
box-shadow: 0 8rpx 22rpx rgba(20, 60, 48, .06);
}
.message-tab {
position: relative;
flex: 1;
height: 58rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 24rpx;
color: #71857f;
font-size: 25rpx;
font-weight: 900;
}
.message-tab.active {
background: linear-gradient(135deg, #37c990, #19af7e);
color: #fff;
}
.tab-badge,
.card-badge {
min-width: 30rpx;
height: 30rpx;
padding: 0 8rpx;
border-radius: 18rpx;
background: #ff4d5f;
color: #fff;
font-size: 18rpx;
font-weight: 900;
line-height: 30rpx;
text-align: center;
box-sizing: border-box;
}
.tab-badge {
margin-left: 8rpx;
}
.card-badge {
margin-left: 8rpx;
flex-shrink: 0;
}
.session-list {
height: calc(100vh - 190rpx);
height: calc(100vh - 280rpx);
}
.session-card {

582
package2/IdleTrad/wantList.vue

@ -0,0 +1,582 @@
<template>
<view class="want-page">
<view class="fixed-header" :style="{ paddingTop: statusBarTop + 'px' }">
<view class="nav-row">
<view class="back" @tap="back"><uni-icons type="left" size="22" color="#15362f"></uni-icons></view>
<view class="title-wrap">
<view class="title">求购广场</view>
<view class="subtitle">看看同学们正在找什么</view>
</view>
</view>
<view class="search-bar">
<uni-icons type="search" size="19" color="#8d9693"></uni-icons>
<input
v-model.trim="keyword"
class="search-input"
type="text"
confirm-type="search"
placeholder="按标题搜索求购"
placeholder-class="search-placeholder"
@confirm="handleSearch"
/>
<view class="search-button" @tap="handleSearch">搜索</view>
</view>
<view class="sort-row">
<view class="sort-item" :class="{ active: sortType == item.key }" v-for="item in sortList" :key="item.key" @tap="changeSort(item.key)">
{{item.name}}
</view>
</view>
<scroll-view class="category-scroll" scroll-x :show-scrollbar="false">
<view class="category-inner">
<view
class="category-item"
:class="{ active: categoryIndex == index }"
v-for="(item,index) in categoryOptions"
:key="item.key"
@tap="changeCategory(index)"
>{{item.name}}</view>
</view>
</scroll-view>
</view>
<scroll-view
class="want-scroll"
scroll-y
:style="{ paddingTop: headerHeight + 'px' }"
:show-scrollbar="false"
@scrolltolower="loadMore"
>
<view class="scroll-content">
<view class="goods-grid" v-if="wantList.length">
<view class="waterfall-column" v-for="(column,columnIndex) in waterfallColumns" :key="columnIndex">
<view class="goods-card" v-for="item in column" :key="item.id" @tap="goDetail(item)">
<view class="goods-image-box" :style="{ height: getImageHeight(item) + 'rpx' }">
<image
v-if="item.pic"
class="goods-image"
:src="item.pic"
mode="aspectFill"
:data-want-id="item.id"
@load="onImageLoad"
></image>
<view v-else class="no-image">暂无图片</view>
<view class="distance-tag">{{item.regionName}}</view>
</view>
<view class="goods-info">
<view class="goods-title">{{item.text}}</view>
<view class="goods-tags">
<text v-if="item.category">{{item.category}}</text>
<text>求购</text>
</view>
<view class="price-row">
<view class="current-price"><text>预算 ¥</text>{{item.price}}</view>
</view>
<view class="seller-row">
<image class="seller-avatar" :src="item.icon" mode="aspectFill"></image>
<text class="seller-name">{{item.shopName}}</text>
<view class="want-count">
<uni-icons type="eye" size="16" color="#a5aeab"></uni-icons>
<text>{{item.browseCount}}</text>
</view>
</view>
</view>
</view>
</view>
</view>
<view class="empty-state" v-else-if="!loading">
<uni-icons type="search" size="38" color="#b8c2be"></uni-icons>
<view>没有找到相关求购</view>
<text>换个关键词试试吧</text>
</view>
<view class="load-more" v-if="loading">加载中...</view>
<view class="load-more" v-else-if="wantList.length && pageNum >= totalPages">没有更多了</view>
<view class="bottom-space"></view>
</view>
</scroll-view>
</view>
</template>
<script>
import { pageFishWants, listFishCategories } from '@/common/fishMarketApi.js'
export default {
data() {
return {
statusBarTop: 24,
keyword: '',
sortType: 'latest',
sortList: [
{ key: 'latest', name: '最新' },
{ key: 'hot', name: '热门' },
{ key: 'priceAsc', name: '预算低到高' },
{ key: 'priceDesc', name: '预算高到低' }
],
categoryIndex: 0,
categoryList: [],
wantList: [],
pageNum: 1,
pageSize: 10,
totalPages: 1,
loading: false,
defaultCover: '/static/images/img/songshu.png'
}
},
computed: {
headerHeight() {
return this.statusBarTop + uni.upx2px(302)
},
categoryOptions() {
return [{ id: '', name: '全部', key: 'all' }].concat(this.categoryList.map(item => ({
...item,
key: item.id || item.name
})))
},
waterfallColumns() {
const columns = [[], []]
const columnHeights = [0, 0]
this.wantList.forEach(item => {
const targetColumn = columnHeights[0] <= columnHeights[1] ? 0 : 1
columns[targetColumn].push(item)
columnHeights[targetColumn] += this.getImageHeight(item) + 190
})
return columns
}
},
onLoad() {
if (uni.getMenuButtonBoundingClientRect) {
this.statusBarTop = uni.getMenuButtonBoundingClientRect().top || this.statusBarTop
}
this.loadCategories()
this.reload()
},
methods: {
back() {
uni.navigateBack()
},
handleSearch() {
uni.hideKeyboard()
this.reload()
},
changeSort(key) {
if (this.sortType == key) return
this.sortType = key
this.reload()
},
changeCategory(index) {
if (this.categoryIndex == index) return
this.categoryIndex = index
this.reload()
},
loadCategories() {
const area = this.getArea()
listFishCategories(area.id || '').then(list => {
this.categoryList = Array.isArray(list) ? list : []
}).catch(() => {})
},
reload() {
this.pageNum = 1
this.totalPages = 1
this.wantList = []
this.loadWants()
},
loadMore() {
if (this.loading || this.pageNum >= this.totalPages) return
this.pageNum += 1
this.loadWants()
},
loadWants() {
if (this.loading) return
this.loading = true
const area = this.getArea()
const category = this.categoryOptions[this.categoryIndex] || {}
pageFishWants({
pageNum: this.pageNum,
pageSize: this.pageSize,
titleKeyword: this.keyword,
sortType: this.sortType,
regionId: area.id || '',
categoryId: category.id || '',
status: 1,
auditStatus: 1
}).then(page => {
const records = this.normalizeRecords(page && (page.records || page.content))
this.totalPages = Number(page && (page.pages || page.totalPages)) || 1
this.wantList = this.pageNum == 1 ? records : this.wantList.concat(records)
}).finally(() => {
this.loading = false
})
},
normalizeRecords(records) {
const area = this.getArea()
const fallbackRegionName = area.title || area.name || uni.getStorageSync('schoolName') || '本校'
return (Array.isArray(records) ? records : []).map(item => {
const images = this.parseJsonList(item.images)
const imageList = item.imageList && item.imageList.length ? item.imageList : images
return {
raw: item,
id: item.id,
pic: item.coverImage || imageList[0] || '',
text: item.title || '',
price: item.expectedPrice || 0,
icon: item.userAvatar || this.defaultCover,
shopName: item.userName || '校园同学',
regionName: item.regionName || item.schoolName || item.areaName || fallbackRegionName,
category: item.categoryName || '',
browseCount: item.browseCount || 0
}
})
},
parseJsonList(value) {
if (!value) return []
if (Array.isArray(value)) return value
try {
const parsed = JSON.parse(value)
return Array.isArray(parsed) ? parsed : []
} catch (e) {
return []
}
},
getArea() {
const area = uni.getStorageSync('area')
if (!area) return {}
if (typeof area === 'string') {
try {
return JSON.parse(area)
} catch (e) {
return {}
}
}
return area
},
getImageHeight(item) {
return Number(item.imageHeight) || 280
},
onImageLoad(event) {
const detail = event && event.detail ? event.detail : {}
const dataset = event && event.currentTarget ? event.currentTarget.dataset || {} : {}
const wantId = String(dataset.wantId || '')
const item = this.wantList.find(want => String(want.id) === wantId)
const width = Number(detail.width)
const height = Number(detail.height)
if (!item || !width || !height) return
const adaptedHeight = Math.max(220, Math.min(430, Math.round(342 * height / width)))
if (item.imageHeight !== adaptedHeight) {
this.$set(item, 'imageHeight', adaptedHeight)
}
},
goDetail(item) {
if (!item || !item.id) return
uni.navigateTo({
url: '/package2/IdleTrad/detail?type=want&id=' + item.id
})
}
}
}
</script>
<style lang="scss">
page {
width: 100%;
height: 100%;
background: #f7f9f7;
color: #17241f;
}
.want-page {
width: 100%;
height: 100%;
overflow: hidden;
background: linear-gradient(180deg, rgba(224, 250, 241, 0.78) 0, rgba(247, 249, 247, 0) 340rpx), #f7f9f7;
}
.fixed-header {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 20;
padding-left: 26rpx;
padding-right: 26rpx;
padding-bottom: 18rpx;
background: linear-gradient(180deg, #e4faf1 0%, rgba(247, 249, 247, 0.96) 100%);
box-shadow: 0 8rpx 24rpx rgba(22, 85, 66, 0.06);
}
.nav-row {
height: 72rpx;
display: flex;
align-items: center;
}
.back {
width: 64rpx;
height: 64rpx;
display: flex;
align-items: center;
}
.title-wrap {
flex: 1;
}
.title {
color: #17362f;
font-size: 34rpx;
font-weight: 900;
}
.subtitle {
margin-top: 2rpx;
color: #739187;
font-size: 20rpx;
}
.search-bar {
height: 68rpx;
margin-top: 10rpx;
display: flex;
align-items: center;
padding: 0 16rpx 0 22rpx;
border-radius: 38rpx;
background: #fff;
box-shadow: 0 10rpx 30rpx rgba(31, 73, 58, 0.08);
}
.search-input {
flex: 1;
height: 68rpx;
margin-left: 10rpx;
font-size: 25rpx;
color: #21342e;
}
.search-placeholder {
color: #9ba7a3;
}
.search-button {
min-width: 88rpx;
height: 52rpx;
line-height: 52rpx;
text-align: center;
border-radius: 28rpx;
color: #fff;
font-size: 23rpx;
font-weight: 800;
background: linear-gradient(135deg, #37c990, #19af7e);
}
.sort-row {
margin-top: 18rpx;
display: flex;
gap: 14rpx;
overflow: hidden;
}
.sort-item {
padding: 12rpx 18rpx;
border-radius: 999rpx;
background: rgba(255, 255, 255, 0.72);
color: #62736e;
font-size: 23rpx;
font-weight: 800;
white-space: nowrap;
}
.sort-item.active {
color: #15362f;
background: #c8f7e2;
}
.category-scroll {
margin-top: 16rpx;
width: 100%;
white-space: nowrap;
}
.category-inner {
display: inline-flex;
align-items: center;
gap: 14rpx;
padding-right: 10rpx;
}
.category-item {
padding: 12rpx 22rpx;
border-radius: 999rpx;
background: rgba(255, 255, 255, .76);
color: #6f817c;
font-size: 23rpx;
font-weight: 800;
}
.category-item.active {
background: linear-gradient(135deg, #37c990, #19af7e);
color: #fff;
box-shadow: 0 8rpx 18rpx rgba(25, 175, 126, .16);
}
.want-scroll {
height: 100%;
box-sizing: border-box;
}
.scroll-content {
padding: 24rpx 24rpx 0;
box-sizing: border-box;
}
.goods-grid {
display: flex;
gap: 18rpx;
align-items: flex-start;
}
.waterfall-column {
flex: 1;
min-width: 0;
}
.goods-card {
overflow: hidden;
margin-bottom: 18rpx;
border-radius: 26rpx;
background: #fff;
box-shadow: 0 10rpx 28rpx rgba(20, 60, 48, 0.06);
}
.goods-image-box {
position: relative;
width: 100%;
min-height: 220rpx;
background: #edf4f1;
}
.goods-image {
width: 100%;
height: 100%;
display: block;
}
.no-image {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: #92a39d;
background: linear-gradient(135deg, #eef6f2, #f8fbf9);
font-size: 25rpx;
font-weight: 900;
}
.distance-tag {
position: absolute;
left: 14rpx;
bottom: 14rpx;
padding: 6rpx 14rpx;
border-radius: 20rpx;
color: #fff;
background: rgba(17, 39, 32, 0.58);
font-size: 20rpx;
font-weight: 700;
}
.goods-info {
padding: 18rpx 18rpx 20rpx;
}
.goods-title {
color: #17241f;
font-size: 27rpx;
line-height: 38rpx;
font-weight: 800;
}
.goods-tags {
margin-top: 12rpx;
display: flex;
flex-wrap: wrap;
gap: 8rpx;
}
.goods-tags text {
padding: 5rpx 12rpx;
border-radius: 14rpx;
color: #3bb68a;
background: #e8fbf3;
font-size: 19rpx;
font-weight: 800;
}
.price-row {
margin-top: 14rpx;
display: flex;
align-items: baseline;
}
.current-price {
color: #ef4c3f;
font-size: 32rpx;
font-weight: 900;
}
.current-price text {
font-size: 21rpx;
}
.seller-row {
margin-top: 14rpx;
display: flex;
align-items: center;
color: #86928e;
font-size: 21rpx;
}
.seller-avatar {
width: 34rpx;
height: 34rpx;
margin-right: 8rpx;
border-radius: 50%;
background: #edf4f1;
}
.seller-name {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.want-count {
display: flex;
align-items: center;
gap: 4rpx;
}
.empty-state,
.load-more {
padding: 90rpx 0;
color: #9aa8a4;
text-align: center;
font-size: 24rpx;
}
.empty-state view {
margin-top: 18rpx;
color: #65736f;
font-size: 27rpx;
font-weight: 900;
}
.empty-state text {
display: block;
margin-top: 8rpx;
font-size: 22rpx;
}
.bottom-space {
height: 80rpx;
}
</style>

190
package2/myCenter/myPost.vue

@ -5,9 +5,10 @@
<view class="title">我的校园二手</view>
</view>
<view class="tabs">
<view class="tab" :class="{ active: activeTab == item.key }" v-for="item in tabs" :key="item.key" @tap="switchTab(item.key)">
<view class="tab" :class="{ active: activeTab == item.key }" v-for="item in visibleTabs" :key="item.key" @tap="switchTab(item.key)">
{{item.name}}
</view>
<view class="tab tab-more" @tap="showTabActions">展开</view>
</view>
<view class="search-bar" v-if="canManageCurrentTab">
<uni-icons type="search" size="18" color="#8d9693"></uni-icons>
@ -31,11 +32,14 @@
@click="handleSwipeAction"
>
<view class="card" @tap="openItem(item)">
<image class="cover" :src="item.coverImage || item.pic || defaultCover" mode="aspectFill"></image>
<image class="cover" :src="coverText(item)" mode="aspectFill"></image>
<view class="main">
<view class="name">{{nameText(item)}}</view>
<view class="desc">{{descText(item)}}</view>
<view class="meta">{{item.createTime || item.lastBrowseTime || item.lastChatTime || ''}}</view>
<!-- <view class="desc">{{descText(item)}}</view> -->
<view class="meta">
<text v-if="priceText(item)">¥{{priceText(item)}}</text>
<text>{{item.createTime || item.lastBrowseTime || item.lastChatTime || ''}}</text>
</view>
</view>
<view class="status" :class="{ 'status--off': isOffShelf(item) }">{{statusText(item)}}</view>
</view>
@ -43,13 +47,20 @@
</u-swipe-action>
<block v-if="!canManageCurrentTab">
<view class="card" v-for="item in list" :key="item.id" @tap="openItem(item)">
<image class="cover" :src="item.coverImage || item.pic || defaultCover" mode="aspectFill"></image>
<image class="cover" :src="coverText(item)" mode="aspectFill"></image>
<view class="main">
<view class="name">{{nameText(item)}}</view>
<view class="desc">{{descText(item)}}</view>
<view class="meta">{{item.createTime || item.lastBrowseTime || item.lastChatTime || ''}}</view>
<view class="meta">
<text v-if="priceText(item)">¥{{priceText(item)}}</text>
<text>{{item.createTime || item.lastBrowseTime || item.lastChatTime || ''}}</text>
</view>
<view class="status" :class="{ 'status--off': isOffShelf(item) }">{{statusText(item)}}</view>
</view>
<view
class="status"
:class="{ 'status--off': isOffShelf(item), 'status--action': activeTab == 'favorites' || activeTab == 'comments' }"
@tap.stop="handleCardAction(item)"
>{{statusText(item)}}</view>
</view>
</block>
<view class="empty" v-if="!list.length && !loading">暂无数据</view>
@ -68,7 +79,9 @@
pageMyFishFavorites,
pageMyFishComments,
pageMyFishLikes,
pageMyFishComplaints
pageMyFishComplaints,
toggleFishFavorite,
deleteFishComment
} from '@/common/fishMarketApi.js'
export default {
@ -81,6 +94,7 @@
pageSize: 10,
totalPages: 1,
loading: false,
loadedOnce: false,
titleKeyword: '',
defaultCover: '/static/images/img/songshu.png',
tabs: [
@ -96,6 +110,12 @@
computed: {
canManageCurrentTab() {
return this.activeTab == 'goods' || this.activeTab == 'wants'
},
visibleTabs() {
const firstTabs = this.tabs.slice(0, 4)
if (firstTabs.some(item => item.key == this.activeTab)) return firstTabs
const active = this.tabs.find(item => item.key == this.activeTab)
return active ? this.tabs.slice(0, 3).concat(active) : firstTabs
}
},
onLoad() {
@ -104,13 +124,34 @@
}
this.load()
},
onShow() {
if (this.loadedOnce) {
this.reload()
}
},
methods: {
back() {
uni.navigateBack()
},
showTabActions() {
uni.showActionSheet({
itemList: this.tabs.map(item => item.name),
success: (res) => {
const item = this.tabs[res.tapIndex]
if (item) this.switchTab(item.key)
}
})
},
swipeOptions(item) {
const offShelf = this.isOffShelf(item)
return [{
text: '编辑',
style: {
backgroundColor: '#24b979',
color: '#fff',
fontSize: '26rpx'
}
}, {
text: offShelf ? '上架' : '下架',
style: {
backgroundColor: offShelf ? '#24b979' : '#ff9f2d',
@ -130,11 +171,22 @@
const item = this.list[Number(e.name)]
if (!item) return
if (e.index == 0) {
this.editItem(item)
return
}
if (e.index == 1) {
this.toggleShelf(item)
return
}
this.deleteItem(item)
},
editItem(item) {
if (!item || !item.id) return
const type = this.activeTab == 'wants' ? 'want' : 'sell'
uni.navigateTo({
url: `/package1/tabbar/fishRelease?mode=edit&type=${type}&id=${item.id}`
})
},
isOffShelf(item) {
return Number(item.status) == 2 || Number(item.offShelf) == 1
},
@ -209,21 +261,71 @@
this.list = this.pageNum == 1 ? records : this.list.concat(records)
}).finally(() => {
this.loading = false
this.loadedOnce = true
})
},
openItem(item) {
if (this.activeTab == 'wants') {
uni.navigateTo({ url: '/package2/IdleTrad/detail?type=want&id=' + item.id })
return
}
const goodsId = item.goodsId || (this.activeTab == 'goods' ? item.id : '')
if (goodsId) uni.navigateTo({ url: '/package2/IdleTrad/detail?id=' + goodsId })
},
statusText(item) {
if (this.activeTab == 'favorites') return '取消收藏'
if (this.activeTab == 'comments') return '删除评价'
if (this.activeTab == 'likes') return item.likeType == 'comment' ? '评论点赞' : '商品点赞'
if (this.activeTab == 'complaints') return item.status == 1 ? '已通过' : (item.status == 2 ? '已驳回' : '待处理')
if (this.activeTab == 'complaints') return item.targetTypeText || (item.status == 1 ? '已通过' : (item.status == 2 ? '已驳回' : '待处理'))
if (item.saleStatus == 2) return '已预定'
if (item.saleStatus == 3) return '已卖出'
if (item.status == 2) return '已下架'
return '正常'
},
handleCardAction(item) {
if (this.activeTab == 'favorites') {
this.cancelFavorite(item)
return
}
if (this.activeTab == 'comments') {
this.deleteCommentItem(item)
}
},
cancelFavorite(item) {
if (!item || !item.goodsId) return
uni.showModal({
title: '取消收藏',
content: '确定取消收藏这件商品吗?',
confirmText: '取消收藏',
confirmColor: '#ef4c3f',
success: (res) => {
if (!res.confirm) return
toggleFishFavorite(item.goodsId).then(result => {
if (!result && result !== false) return
uni.showToast({ title: '已取消收藏', icon: 'none' })
this.reload()
})
}
})
},
deleteCommentItem(item) {
if (!item || !item.id) return
uni.showModal({
title: '删除评价',
content: '确定删除这条评价吗?',
confirmText: '删除',
confirmColor: '#ef4c3f',
success: (res) => {
if (!res.confirm) return
deleteFishComment(item.id).then(() => {
uni.showToast({ title: '已删除', icon: 'none' })
this.reload()
})
}
})
},
nameText(item) {
if (item.displayTitle) return item.displayTitle
if (item.title) return item.title
if (this.activeTab == 'comments') return '我发表的评论'
if (this.activeTab == 'likes') return item.likeType == 'comment' ? '我点赞的评论' : '我点赞的商品'
@ -231,11 +333,43 @@
return item.goodsId || item.id || '校园二手'
},
descText(item) {
if (this.activeTab == 'favorites') return '商品ID:' + item.goodsId
if (this.activeTab == 'favorites') return '收藏的商品'
if (this.activeTab == 'comments') return item.content || '评论内容'
if (this.activeTab == 'likes') return '关联商品:' + (item.goodsId || '评论 ' + (item.commentId || ''))
if (this.activeTab == 'complaints') return item.reason || '举报内容'
return item.content || ''
if (this.activeTab == 'likes') return item.likeType == 'comment' ? (item.commentContent || '点赞了评论') : '点赞了商品'
if (this.activeTab == 'complaints') {
const prefix = item.targetTypeText ? item.targetTypeText + ':' : ''
const detail = item.commentContent || item.reason || '举报内容'
return prefix + detail
}
return this.plainText(item.content || '')
},
coverText(item) {
return item.displayCover || item.coverImage || item.pic || this.firstImage(item.images) || this.defaultCover
},
priceText(item) {
if (item.displayPrice !== undefined && item.displayPrice !== null && item.displayPrice !== '') return item.displayPrice
if (this.activeTab == 'wants') return item.expectedPrice || ''
return item.price || ''
},
firstImage(value) {
if (!value) return ''
if (Array.isArray(value)) return value[0] || ''
try {
const parsed = JSON.parse(value)
return Array.isArray(parsed) ? (parsed[0] || '') : ''
} catch (e) {
return ''
}
},
plainText(value) {
return String(value || '')
.replace(/<img[^>]*>/gi, '[图片]')
.replace(/<[^>]+>/g, '')
.replace(/&nbsp;/g, ' ')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&amp;/g, '&')
.trim()
}
}
}
@ -268,24 +402,32 @@
}
.tabs {
display: flex;
overflow-x: scroll;
align-items: center;
overflow: visible;
padding: 0 22rpx 18rpx;
white-space: nowrap;
}
.tab {
flex-shrink: 0;
margin-right: 18rpx;
padding: 14rpx 24rpx;
margin-right: 12rpx;
padding: 13rpx 18rpx;
border-radius: 28rpx;
background: #fff;
color: #758782;
font-size: 24rpx;
font-size: 22rpx;
}
.tab.active {
background: #24b979;
color: #fff;
font-weight: 900;
}
.tab-more {
margin-left: auto;
margin-right: 0;
background: #e8fbf3;
color: #18a878;
font-weight: 900;
}
.search-bar {
height: 70rpx;
margin: 0 24rpx 16rpx;
@ -364,18 +506,32 @@
}
.meta {
margin-top: 8rpx;
display: flex;
gap: 14rpx;
color: #a0aaa6;
font-size: 20rpx;
}
.meta text:first-child {
color: #ef4c3f;
font-weight: 900;
}
.status {
margin-left: 12rpx;
color: #22a879;
font-size: 22rpx;
font-weight: 800;
white-space: nowrap;
}
.status--off {
color: #9aa8a4;
}
.status--action {
padding: 8rpx 14rpx;
border-radius: 18rpx;
background: #fff2f2;
color: #ef4c3f;
font-size: 20rpx;
}
.empty,
.load-more {
padding: 80rpx 0;

Loading…
Cancel
Save