添加朋友圈功能

This commit is contained in:
bobobobo
2026-01-13 01:12:29 +08:00
parent ac67fa30f4
commit 06e026c8b8
8 changed files with 319 additions and 44 deletions

View File

@@ -197,3 +197,46 @@ export const getUserTradeRecordList = data => {
data data
}) })
} }
/** 发布朋友圈 */
export const addUserMoments = data => {
return http({
url: '/api/service/userMoments',
method: 'post',
data
})
}
/** 获取朋友圈列表 */
export const getUserMomentsList = data => {
return http({
url: '/api/service/userMoments/list',
method: 'get',
data
})
}
/** 点赞 */
export const likeUserMoments = id => {
return http({
url: `/api/service/userMoments/like/${id}`,
method: 'put'
})
}
/** 发布评论 */
export const addUserMomentsComment = data => {
return http({
url: '/api/service/userMoments/comment',
method: 'post',
data
})
}
/** 删除 */
export const deleteUserMoments = id => {
return http({
url: `/api/service/userMoments/${id}`,
method: 'delete'
})
}

View File

@@ -1,72 +1,171 @@
<script setup> <script setup>
import { reactive, ref } from 'vue' import { reactive, ref } from 'vue'
import { onPageScroll } from '@dcloudio/uni-app'
import { navigateTo } from '@/utils/router' import { navigateTo } from '@/utils/router'
import {
addUserMomentsComment,
deleteUserMoments,
likeUserMoments,
getUserMomentsList
} from '@/api/my-index'
import { onLoad, onShow } from '@dcloudio/uni-app'
import { useAuthUser } from '@/composables/useAuthUser'
import { formatRelativeTime } from '@/utils/dateUtils'
import { useUI } from '@/utils/use-ui'
const { userInfo } = useAuthUser()
const { showDialog, showToast } = useUI()
const placeholderStyle = `font-family: PingFang SC, PingFang SC;
font-weight: 500;
font-size: 28rpx;
color: #999999;
line-height: 40rpx;
font-style: normal;
text-transform: none;`
const MAX_SCROLL = 446 const MAX_SCROLL = 446
const paging = ref(null)
const cbNavBar = ref({}) const cbNavBar = ref({})
const dataList = ref([])
const topIcon = reactive({ const topIcon = reactive({
leftColor: '#ffffff', leftColor: '#ffffff',
rightColor: '#ffffff' rightColor: '#ffffff'
}) })
const formData = reactive({
type: '',
pageNum: 1,
pageSize: 15
})
const contentData = ref('')
const inputId = ref('')
onPageScroll(e => { const onScroll = e => {
cbNavBar.value?.updateScroll(e.scrollTop) cbNavBar.value?.updateScroll(e.detail.scrollTop)
if (e.scrollTop > MAX_SCROLL - 220) { if (e.detail.scrollTop > MAX_SCROLL - 220) {
topIcon.leftColor = '#000' topIcon.leftColor = '#000'
topIcon.rightColor = '#000' topIcon.rightColor = '#000'
} else { } else {
topIcon.leftColor = '#ffffff' topIcon.leftColor = '#ffffff'
topIcon.rightColor = '#ffffff' topIcon.rightColor = '#ffffff'
} }
}
const getData = async (pageNum, pageSize) => {
try {
const res = await getUserMomentsList({
pageNum,
pageSize
})
paging.value.complete(res.rows)
} catch (error) {
paging.value.complete(false)
}
}
const onLike = async item => {
await likeUserMoments(item.id)
// item.likeCount += 1
}
/** 点击查看大图 */
const onImage = current => {
uni.previewImage({
urls: [current], // 图片路径数组(本地或网络)
current: current // 当前显示的图片(可选,默认为第一张)
})
closeComment()
}
/** 关闭评论框 */
const closeComment = () => {
contentData.value = ''
inputId.value = ''
}
/** 发布评论 */
const onComment = async item => {
console.log('发布评论')
const data = {
content: contentData.value,
id: item.id
}
const res = await addUserMomentsComment(data)
closeComment()
}
/** 删除动态 */
const onDeleteItem = async id => {
const res = await showDialog('提示', '确定要删除吗?')
if (!res) return
await deleteUserMoments(id)
await showToast('删除成功', 'success')
dataList.value = dataList.value.filter(item => item.id !== id)
}
onShow(() => {
getData(1, formData.pageSize)
}) })
onLoad(async () => {})
</script> </script>
<template> <template>
<view class="dynamic"> <z-paging
<nav-bar ref="paging"
ref="cbNavBar" v-model="dataList"
isTopBg :default-page-no="formData.pageNum"
target-color="#f9f9f9" :default-page-size="formData.pageSize"
:max-scroll="MAX_SCROLL" safe-area-inset-bottom
> use-safe-area-placeholder
<template #back> :show-loading-more-no-more-view="false"
<uni-icons @query="getData"
:color="topIcon.leftColor" @scroll="onScroll"
type="left" >
size="24" <template #top>
></uni-icons> <nav-bar
</template> ref="cbNavBar"
<template #right> isTopBg
<uni-icons target-color="#f9f9f9"
:color="topIcon.rightColor" :max-scroll="MAX_SCROLL"
type="camera" >
size="24" <template #back>
@click="navigateTo('/pages/discover/dynamic/release')" <uni-icons
></uni-icons> :color="topIcon.leftColor"
</template> type="left"
</nav-bar> size="24"
></uni-icons>
</template>
<template #right>
<uni-icons
:color="topIcon.rightColor"
type="camera"
size="24"
@click="navigateTo('/pages/discover/dynamic/release')"
></uni-icons>
</template>
</nav-bar>
</template>
<view class="top-bg-img"> <view class="top-bg-img">
<image <image
src="https://wx4.sinaimg.cn/mw690/006i0nC8ly1hqugav3k6yj31o01o0aod.jpg" :src="userInfo.avatar"
mode="aspectFill" mode="aspectFill"
class="img" class="img"
@click="onImage(userInfo.avatar)"
></image> ></image>
<!-- 用户信息 --> <!-- 用户信息 -->
<view class="user-info"> <view class="user-info">
<text>名字</text> <text>{{ userInfo.userName }}</text>
<image <image
src="https://img2.baidu.com/it/u=3408192385,656358498&fm=253&app=138&f=JPEG?w=760&h=760" :src="userInfo.avatar"
mode="aspectFill" mode="aspectFill"
class="avatar" class="avatar"
@click="onImage(userInfo.avatar)"
></image> ></image>
</view> </view>
</view> </view>
<!-- 动态列表 --> <!-- 动态列表 -->
<view class="dynamic-list"> <view class="dynamic-list" @click="closeComment">
<view v-for="item in 4" class="list"> <view v-for="item in dataList" :key="item.id" class="list">
<image <image
src="https://img1.baidu.com/it/u=2645961124,1296922095&fm=253&app=138&f=JPEG?w=800&h=1530" src="https://img1.baidu.com/it/u=2645961124,1296922095&fm=253&app=138&f=JPEG?w=800&h=1530"
mode="aspectFill" mode="aspectFill"
@@ -74,7 +173,7 @@
></image> ></image>
<view class="content"> <view class="content">
<text class="name">名字</text> <text class="name">名字</text>
<text class="text">这是一条朋友圈的标题</text> <text class="text">{{ item.content }}</text>
<view class="img-list"> <view class="img-list">
<image <image
src="https://p4.itc.cn/images01/20220619/46660ed163164c14be90e605a73ee5e8.jpeg" src="https://p4.itc.cn/images01/20220619/46660ed163164c14be90e605a73ee5e8.jpeg"
@@ -84,20 +183,44 @@
</view> </view>
<!-- 地址 --> <!-- 地址 -->
<view class="address"> <view class="address">
<text>19分钟前</text> <text>{{ formatRelativeTime(item.createTime) }}</text>
<text>重庆市渝北xxx寄街道</text> <!-- <text>重庆市渝北xxx寄街道</text> -->
</view> </view>
<!-- 点赞评论 --> <!-- 点赞评论 -->
<view class="like-box"> <view class="like-box">
<view class="like"> <view class="like" @click.stop="onLike(item)">
<uni-icons <uni-icons
type="hand-up" type="hand-up"
size="20" size="20"
color="#747474" color="#747474"
></uni-icons> ></uni-icons>
<text>22</text> <text v-if="item.likeCount > 0">{{ item.likeCount }}</text>
</view> </view>
<uni-icons type="chat" size="20" color="#747474"></uni-icons> <uni-icons
type="chat"
size="20"
color="#747474"
@click.stop="inputId = item.id"
></uni-icons>
<uni-icons
type="trash"
size="20"
color="#d95d5d"
style="margin-left: 86rpx"
@click.stop="onDeleteItem(item.id)"
></uni-icons>
</view>
<view v-if="inputId === item.id" class="input-box">
<input
v-model="contentData"
focus
confirm-type="done"
placeholder="评论"
:placeholder-style="placeholderStyle"
@confirm.stop="onComment(item)"
/>
<button @click.stop="onComment(item)">发布</button>
</view> </view>
<!-- 评论内容 --> <!-- 评论内容 -->
<view class="comment"> <view class="comment">
@@ -109,7 +232,7 @@
</view> </view>
</view> </view>
</view> </view>
</view> </z-paging>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@@ -1,5 +1,8 @@
<script setup> <script setup>
import { reactive } from 'vue' import { reactive } from 'vue'
import { addUserMoments } from '@/api/my-index'
import { useUI } from '@/utils/use-ui'
import { navigateBack } from '@/utils/router'
const placeholderStyle = `font-family: PingFang SC, PingFang SC; const placeholderStyle = `font-family: PingFang SC, PingFang SC;
font-weight: 500; font-weight: 500;
@@ -9,13 +12,31 @@ line-height: 40rpx;
font-style: normal; font-style: normal;
text-transform: none;` text-transform: none;`
const { showToast } = useUI()
const formData = reactive({ const formData = reactive({
txt: '', txt: '',
listImg: [] listImg: []
}) })
const onUpData = () => { const onUpData = async () => {
console.log(formData) if (!formData.txt) {
showToast('请输入内容')
return
}
await addUserMoments({
content: formData.txt,
privacy: 0,
images: formData.listImg.map(v => {
return {
imageUrl: v
}
})
})
await showToast('发布成功', 'success')
navigateBack()
} }
</script> </script>

View File

@@ -79,7 +79,7 @@
font-size: 24rpx; font-size: 24rpx;
color: #999999; color: #999999;
margin-right: 16rpx; margin-right: 16rpx;
&:last-child { &:nth-child(2) {
font-size: 24rpx; font-size: 24rpx;
color: #0c587e; color: #0c587e;
} }
@@ -121,6 +121,32 @@
} }
} }
} }
.input-box {
margin-top: 16rpx;
display: flex;
justify-content: space-between;
align-items: center;
border-radius: 14rpx;
border: 2rpx solid #19ac31;
padding: 12rpx 18rpx;
input {
font-size: 28rpx;
}
button {
display: inline-block;
margin: 0;
width: 120rpx;
height: 60rpx;
line-height: 60rpx;
background: #19ac31;
border-radius: 8rpx;
font-size: 24rpx;
color: #ffffff;
&::after {
display: none;
}
}
}
} }
} }
} }

View File

@@ -9,7 +9,7 @@
width: 100%; width: 100%;
height: 38rpx; height: 38rpx;
left: 0; left: 0;
bottom: 0; bottom: -2rpx;
background: #ffffff; background: #ffffff;
border-radius: 32rpx 32rpx 0rpx 0rpx; border-radius: 32rpx 32rpx 0rpx 0rpx;
} }

View File

@@ -89,6 +89,7 @@
:default-page-size="formData.pageSize" :default-page-size="formData.pageSize"
safe-area-inset-bottom safe-area-inset-bottom
use-safe-area-placeholder use-safe-area-placeholder
:show-loading-more-no-more-view="false"
@query="getData" @query="getData"
> >
<template #top> <template #top>

View File

@@ -6,10 +6,13 @@
import { navigateTo } from '@/utils/router' import { navigateTo } from '@/utils/router'
const list = ref([]) const list = ref([])
const loading = ref(true)
const getData = async () => { const getData = async () => {
loading.value = true
const res = await getMyGroupList() const res = await getMyGroupList()
list.value = res.data list.value = res.data
console.log(res.data) console.log(res.data)
loading.value = false
} }
const onGo = id => { const onGo = id => {
@@ -22,7 +25,8 @@
</script> </script>
<template> <template>
<view class="shop-together"> <view v-if="!loading" class="shop-together">
<cb-empty v-if="!loading && list.length === 0"></cb-empty>
<view <view
v-for="item in list" v-for="item in list"
:key="item.id" :key="item.id"

View File

@@ -23,3 +23,60 @@ export const formatMonthDay = date => {
return `${month}.${day}` return `${month}.${day}`
} }
/**
* 将时间字符串转换为相对时间描述
* @param {string} timeStr - 后端返回的时间字符串,如 '2026-01-12 22:51:54'
* @returns {string} 相对时间描述,如 '刚刚'、'3分钟前'、'昨天' 等
*/
export function formatRelativeTime(timeStr) {
// 兼容 iOS 不支持 '-' 分隔的日期格式,需转为标准 ISO 格式
const normalizedTimeStr = timeStr.replace(/-/g, '/')
const serverTime = new Date(normalizedTimeStr)
const now = new Date()
// 时间差(毫秒)
const diffMs = now - serverTime
// 如果时间在未来,直接返回原始时间或处理异常
if (diffMs < 0) {
return timeStr // 或者 return '未来时间';
}
const seconds = Math.floor(diffMs / 1000)
const minutes = Math.floor(seconds / 60)
const hours = Math.floor(minutes / 60)
const days = Math.floor(hours / 24)
// 判断是否是今天
const isSameDay = (date1, date2) => {
return (
date1.getFullYear() === date2.getFullYear() &&
date1.getMonth() === date2.getMonth() &&
date1.getDate() === date2.getDate()
)
}
// 判断是否是昨天
const isYesterday = (date1, date2) => {
const yesterday = new Date(date2)
yesterday.setDate(date2.getDate() - 1)
return isSameDay(date1, yesterday)
}
if (seconds < 60) {
return '刚刚'
} else if (minutes < 60) {
return `${minutes}分钟前`
} else if (hours < 24 && isSameDay(serverTime, now)) {
return `${hours}小时前`
} else if (isYesterday(serverTime, now)) {
return '昨天'
} else if (days < 7) {
return `${days}天前`
} else {
// 超过一周,返回原始日期(可选格式化为 YYYY-MM-DD
return timeStr.split(' ')[0] // 或使用更美观的格式
}
}