chore: 重写初始提交(清空历史,整理后全量提交)
This commit is contained in:
@@ -0,0 +1,473 @@
|
||||
<template>
|
||||
<view class="card-panel">
|
||||
<!-- 用户信息 -->
|
||||
<view class="card-panel-header">
|
||||
<view class="user-info" @click="onUserClick">
|
||||
<image class="avatar" :src="moment.userAvatar" mode="aspectFill"></image>
|
||||
<view class="user-details">
|
||||
<view class="user-name-row">
|
||||
<text class="user-name">{{ moment.userName }}</text>
|
||||
<text v-if="moment.userVip" class="vip-badge">V</text>
|
||||
<text v-if="moment.hot" class="hot-badge">🔥</text>
|
||||
</view>
|
||||
<view class="meta-info">
|
||||
<text class="location">{{ moment.location }}</text>
|
||||
<text class="time">{{ formatTime(moment.time) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="more-btn" @click="onMoreClick">
|
||||
<uni-icons type="more" size="20" color="#999"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 动态内容 -->
|
||||
<view class="card-panel-body" @click="onContentClick">
|
||||
<text class="content-text">{{ moment.content }}</text>
|
||||
|
||||
<!-- 图片内容 -->
|
||||
<view v-if="moment.type === 'image' && moment.images.length > 0" class="image-content">
|
||||
<view v-if="moment.images.length == 1" class="single-image" @click="previewImage(0)">
|
||||
<image class="image-item" :src="moment.images[0]" mode="aspectFill"></image>
|
||||
</view>
|
||||
<view v-else-if="moment.images.length < 3" class="multi-images-row">
|
||||
<view v-for="(img, index) in moment.images" :key="index" class="image-item-small"
|
||||
@click="previewImage(index)">
|
||||
<image class="image" :src="img" mode="aspectFill"></image>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="multi-images-grid">
|
||||
<view v-for="(img, index) in moment.images.slice(0, 4)" :key="index" class="grid-image"
|
||||
:class="{ 'last-image': index == 3 && moment.images.length > 4 }" @click="previewImage(index)">
|
||||
<image class="image" :src="img" mode="aspectFill"></image>
|
||||
<view v-if="index == 3 && moment.images.length > 4" class="image-count">
|
||||
+{{ moment.images.length - 4 }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 视频内容 -->
|
||||
<view v-else-if="moment.type === 'video'" class="video-content" @click="playVideo">
|
||||
<image v-if="moment.images.length > 0" class="video-cover" :src="moment.images[0] " mode="aspectFill">
|
||||
</image>
|
||||
<view class="video-play-btn">
|
||||
<uni-icons type="play" size="40" color="#fff"></uni-icons>
|
||||
</view>
|
||||
<view class="video-duration">03:25</view>
|
||||
</view>
|
||||
|
||||
<!-- 标签 -->
|
||||
<view v-if="moment.tags != null" class="moment-tags">
|
||||
<view v-for="(tag, index) in moment.tags" :key="index" class="tag-item">
|
||||
<text class="tag-text">#{{ tag }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 互动操作栏 -->
|
||||
<view class="card-panel-footer">
|
||||
<view class="action-item" @click="onLikeClick">
|
||||
<uni-icons :type="moment.isLiked ? 'heart-filled' : 'heart'" size="20"
|
||||
:color="moment.isLiked ? '#FF2D55' : '#666'"></uni-icons>
|
||||
<text class="action-text" :style="{ color: moment.isLiked ? '#FF2D55' : '#666' }">
|
||||
{{ formatNumber(moment.likes) }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="action-item" @click="onCommentClick">
|
||||
<uni-icons type="chat" size="20" color="#666"></uni-icons>
|
||||
<text class="action-text">{{ formatNumber(moment.comments) }}</text>
|
||||
</view>
|
||||
|
||||
<view class="action-item" @click="onShareClick">
|
||||
<uni-icons type="redo" size="20" color="#666"></uni-icons>
|
||||
<text class="action-text">{{ formatNumber(moment.shares) }}</text>
|
||||
</view>
|
||||
|
||||
<view class="action-item distance" v-if="moment.distance !=0 && moment.distance < 50">
|
||||
<uni-icons type="location" size="16" color="#999"></uni-icons>
|
||||
<text class="distance-text">{{ formatDistance(moment.distance) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { IMoment } from '@/types/dynamicType.uts'
|
||||
|
||||
const props = defineProps<{
|
||||
moment : IMoment
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
like : [id: number]
|
||||
comment : [id: number]
|
||||
share : [id: number]
|
||||
more : [id: number]
|
||||
}>()
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = (timestamp : number) : string => {
|
||||
const now = Date.now()
|
||||
const diff = now - timestamp
|
||||
if (diff < 60000) { // 1分钟内
|
||||
return '刚刚'
|
||||
} else if (diff < 3600000) { // 1小时内
|
||||
return Math.floor(diff / 60000) + '分钟前'
|
||||
} else if (diff < 86400000) { // 1天内
|
||||
return Math.floor(diff / 3600000) + '小时前'
|
||||
} else if (diff < 604800000) { // 1周内
|
||||
return Math.floor(diff / 86400000) + '天前'
|
||||
} else {
|
||||
const date = new Date(timestamp)
|
||||
return `${date.getMonth() + 1}月${date.getDate()}日`
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化数字
|
||||
const formatNumber = (num : number) : string => {
|
||||
if (num >= 10000) {
|
||||
return (num / 10000).toFixed(1) + 'w'
|
||||
} else if (num >= 1000) {
|
||||
return (num / 1000).toFixed(1) + 'k'
|
||||
}
|
||||
return num.toString()
|
||||
}
|
||||
|
||||
// 格式化距离
|
||||
const formatDistance = (distance : number) : string => {
|
||||
if (distance < 1) {
|
||||
return '<1km'
|
||||
} else if (distance < 10) {
|
||||
return distance.toFixed(1) + 'km'
|
||||
} else {
|
||||
return Math.floor(distance) + 'km'
|
||||
}
|
||||
}
|
||||
|
||||
// 点击用户
|
||||
const onUserClick = () => {
|
||||
console.log('点击用户:', props.moment.userName)
|
||||
uni.navigateTo({
|
||||
url: `/pages/user-detail/user-detail?id=${props.moment.userId}`
|
||||
})
|
||||
}
|
||||
|
||||
// 点击更多
|
||||
const onMoreClick = () => {
|
||||
emit('more', props.moment.id)
|
||||
}
|
||||
|
||||
// 点击内容
|
||||
const onContentClick = () => {
|
||||
console.log('点击动态内容:', props.moment.id)
|
||||
let page = `/pages/moment/details?id=${props.moment.id}`;
|
||||
console.log(page)
|
||||
uni.navigateTo({
|
||||
url: page
|
||||
})
|
||||
}
|
||||
|
||||
// 预览图片
|
||||
const previewImage = (index : number) => {
|
||||
console.log('预览图片:', index)
|
||||
uni.previewImage({
|
||||
current: index,
|
||||
urls: props.moment.images
|
||||
})
|
||||
}
|
||||
|
||||
// 播放视频
|
||||
const playVideo = () => {
|
||||
console.log('播放视频')
|
||||
uni.navigateTo({
|
||||
url: `/pages/video-player/video-player?url=${encodeURIComponent(props.moment.videoUrl)}`
|
||||
})
|
||||
}
|
||||
|
||||
// 点赞
|
||||
const onLikeClick = () => {
|
||||
emit('like', props.moment.id)
|
||||
}
|
||||
|
||||
// 评论
|
||||
const onCommentClick = () => {
|
||||
emit('comment', props.moment.id)
|
||||
}
|
||||
|
||||
// 分享
|
||||
const onShareClick = () => {
|
||||
emit('share', props.moment.id)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* 用户信息 */
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
margin-right: 20rpx;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.user-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.user-name-row {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-right: 10rpx;
|
||||
max-width: 200rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.vip-badge {
|
||||
background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
padding: 3rpx 10rpx;
|
||||
border-radius: 20rpx;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.hot-badge {
|
||||
background-color: #f30f025c;
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
padding: 3rpx 10rpx;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.meta-info {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.location,
|
||||
.time {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.more-btn {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 50%;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
/* 动态内容 */
|
||||
.moment-content {
|
||||
margin-bottom: 25rpx;
|
||||
}
|
||||
|
||||
.content-text {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 20rpx;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 图片内容 */
|
||||
.image-content {
|
||||
margin: 20rpx 0;
|
||||
}
|
||||
|
||||
.single-image {
|
||||
width: 100%;
|
||||
height: 400rpx;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.image-item {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.multi-images-row {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.image-item-small {
|
||||
flex: 1;
|
||||
height: 375rpx;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
padding: 5rpx;
|
||||
}
|
||||
|
||||
.image-item-small .image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.multi-images-grid {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.grid-image {
|
||||
margin: 5rpx;
|
||||
position: relative;
|
||||
flex-basis: 210rpx;
|
||||
height: 230rpx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.grid-image .image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.grid-image.last-image::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.image-count {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 40rpx;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 视频内容 */
|
||||
.video-content {
|
||||
position: relative;
|
||||
height: 400rpx;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
margin: 20rpx 0;
|
||||
}
|
||||
|
||||
.video-cover {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.video-play-btn {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.video-duration {
|
||||
position: absolute;
|
||||
bottom: 20rpx;
|
||||
right: 20rpx;
|
||||
padding: 8rpx 20rpx;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
/* 标签 */
|
||||
.moment-tags {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.tag-item {
|
||||
padding: 8rpx 20rpx;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.tag-text {
|
||||
font-size: 24rpx;
|
||||
color: #007AFF;
|
||||
}
|
||||
|
||||
/* 互动操作栏 */
|
||||
.action-bar {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
padding-top: 20rpx;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.action-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 40rpx;
|
||||
padding: 10rpx 0;
|
||||
}
|
||||
|
||||
.action-item .uni-icons {
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.action-text {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.action-item.distance {
|
||||
margin-left: auto;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.distance-text {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,451 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="page-nav">
|
||||
<view class="page-nav-left">
|
||||
<scroll-view class="tabsbox" :show-scrollbar="false" direction="horizontal">
|
||||
<view v-for="(tab, index) in tabs" :key="index" class="tabsbox-item"
|
||||
:class="{ 'tabsbox-active': activeTab === index }" @click="switchTab(index)">
|
||||
<text class="tabsbox-text" :class="{'tabsbox-active-txt': activeTab == index }">{{ tab.name }}</text>
|
||||
<!-- <text v-if="tab.badge" class="badge">{{ tab.badge }}</text> -->
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view class="page-nav-right">
|
||||
<view class="icon-btn" @click="onSearchClick">
|
||||
<uni-icons type="search" size="24" color="#333"></uni-icons>
|
||||
</view>
|
||||
<view class="icon-btn" @click="onPublishClick">
|
||||
<uni-icons type="plusempty" size="24" color="#333"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="page-body" style="">
|
||||
<scroll-view class="moment-list" :show-scrollbar="false" direction="vertical" :refresher-enabled="true" :refresher-triggered="refreshing"
|
||||
@refresherrefresh="onRefresh" @scrolltolower="onLoadMore" style="padding-bottom: 96rpx;">
|
||||
<!-- 置顶动态 -->
|
||||
<view v-if="pinnedMoments.length > 0" class="pinned-section">
|
||||
<text class="section-title">置顶动态</text>
|
||||
<view v-for="moment in pinnedMoments" :key="'pinned-' + moment.id" class="moment-item pinned">
|
||||
<moment-card :moment="moment" @like="onLike" @comment="onComment" @share="onShare"
|
||||
@more="onMore" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 推荐动态 -->
|
||||
<view class="recommend-section" v-if="activeTab == 0">
|
||||
<text class="section-title">推荐动态</text>
|
||||
<view v-for="moment in recommendMoments" :key="'recommend-' + moment.id" class="moment-item">
|
||||
<moment-card :moment="moment" @like="onLike" @comment="onComment" @share="onShare"
|
||||
@more="onMore" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 关注动态 -->
|
||||
<view class="follow-section" v-if="activeTab == 1">
|
||||
<view v-if="followMoments.length > 0">
|
||||
<view v-for="moment in followMoments" :key="'follow-' + moment.id" class="moment-item">
|
||||
<moment-card :moment="moment" @like="onLike" @comment="onComment" @share="onShare"
|
||||
@more="onMore" />
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="empty-state">
|
||||
<uni-icons type="person" size="60" color="#ccc"></uni-icons>
|
||||
<text class="empty-text">还没有关注的好友动态</text>
|
||||
<button class="empty-btn" @click="findFriends">去发现好友</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 附近动态 -->
|
||||
<view class="nearby-section" v-if="activeTab == 2">
|
||||
<view class="section-header">
|
||||
<text class="section-title">附近动态</text>
|
||||
<text class="section-subtitle">{{ nearbyCount }}条动态</text>
|
||||
</view>
|
||||
<view v-for="moment in nearbyMoments" :key="'nearby-' + moment.id" class="moment-item">
|
||||
<moment-card :moment="moment" @like="onLike" @comment="onComment" @share="onShare"
|
||||
@more="onMore" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 热门动态 -->
|
||||
<view class="hot-section" v-if="activeTab == 3">
|
||||
<view class="section-header">
|
||||
<text class="section-title">热门动态</text>
|
||||
<view class="hot-tags">
|
||||
<view v-for="(tag, index) in hotTags" :key="index" class="hot-tag"
|
||||
:class="{ active: activeHotTag == index }" @click="switchHotTag(index)">
|
||||
<text class="tag-text">{{ tag }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-for="moment in hotMoments" :key="'hot-' + moment.id" class="moment-item">
|
||||
<moment-card :moment="moment" @like="onLike" @comment="onComment" @share="onShare"
|
||||
@more="onMore" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<!-- <view class="load-status">
|
||||
<uni-load-more :status="loadStatus" />
|
||||
</view> -->
|
||||
</scroll-view>
|
||||
|
||||
<!-- 发布按钮 -->
|
||||
<view class="publish-fab" @click="onPublishClick">
|
||||
<uni-icons type="plus" size="30" color="#fff"></uni-icons>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import MomentCard from './components/MomentCard.uvue'
|
||||
import {IMoment,ITab} from '@/types/dynamicType.uts'
|
||||
import Api from '@/common/api-service.uts'
|
||||
|
||||
// 响应式数据
|
||||
const activeTab = ref(0)
|
||||
const activeHotTag = ref(0)
|
||||
const refreshing = ref(false)
|
||||
const loading = ref(false)
|
||||
const noMore = ref(false)
|
||||
const page = ref(1)
|
||||
const pageSize = 10
|
||||
const moments = reactive<IMoment[]>([])
|
||||
const scopes = ['recommend', 'follow', 'nearby', 'hot']
|
||||
const nearbyCount = computed(() => moments.length)
|
||||
|
||||
// 标签页数据
|
||||
const tabs = reactive<ITab[]>([
|
||||
{ name: '推荐', badge: 0 },
|
||||
{ name: '关注', badge: 3 },
|
||||
{ name: '附近', badge: 12 },
|
||||
{ name: '热门', badge: 0 }
|
||||
])
|
||||
|
||||
// 热门标签
|
||||
const hotTags = reactive(['全部', '生活', '情感', '旅行', '美食', '游戏'])
|
||||
|
||||
// 计算属性
|
||||
const pinnedMoments = computed<IMoment[]>(() => moments.filter(m => m.isPinned))
|
||||
const recommendMoments = computed<IMoment[]>(() => moments)
|
||||
const followMoments = computed<IMoment[]>(() => moments)
|
||||
const nearbyMoments = computed<IMoment[]>(() => moments)
|
||||
const hotMoments = computed<IMoment[]>(() => moments)
|
||||
|
||||
const loadStatus = computed(() => {
|
||||
if (loading.value) return 'loading'
|
||||
if (noMore.value) return 'noMore'
|
||||
return 'more'
|
||||
})
|
||||
|
||||
// 加载数据
|
||||
const loadData = (reset : boolean = false) => {
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
const p = reset ? 1 : page.value
|
||||
Api.moment.list({ scope: scopes[activeTab.value], page: p, page_size: pageSize })
|
||||
.then((res : UTSJSONObject) => {
|
||||
const list = (res.get('list') as Array<IMoment>) ?? []
|
||||
if (reset) {
|
||||
moments.splice(0, moments.length)
|
||||
page.value = 1
|
||||
}
|
||||
list.forEach((m : IMoment) => moments.push(m))
|
||||
page.value = p + 1
|
||||
noMore.value = list.length < pageSize
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => { loading.value = false })
|
||||
}
|
||||
|
||||
// 页面加载
|
||||
onMounted(() => { loadData(true) })
|
||||
onShow(() => { loadData(true) })
|
||||
|
||||
// 切换标签页
|
||||
const switchTab = (index : number) => {
|
||||
activeTab.value = index
|
||||
page.value = 1
|
||||
noMore.value = false
|
||||
loadData(true)
|
||||
}
|
||||
|
||||
// 切换热门标签
|
||||
const switchHotTag = (index : number) => {
|
||||
activeHotTag.value = index
|
||||
console.log('切换热门标签:', hotTags[index])
|
||||
// 这里应该根据标签筛选热门动态
|
||||
}
|
||||
|
||||
// 点击搜索
|
||||
const onSearchClick = () => {
|
||||
console.log('点击搜索')
|
||||
uni.navigateTo({
|
||||
url: '/pages/search/search'
|
||||
})
|
||||
}
|
||||
|
||||
// 点击发布
|
||||
const onPublishClick = () => {
|
||||
console.log('点击发布')
|
||||
uni.navigateTo({
|
||||
url: '/pages/moment/publish'
|
||||
})
|
||||
}
|
||||
|
||||
// 点赞动态
|
||||
const onLike = (momentId : number) => {
|
||||
const m = moments.find(x => x.id === momentId)
|
||||
if (! m) return
|
||||
const willLike = ! m.isLiked
|
||||
m.isLiked = willLike
|
||||
m.likes += willLike ? 1 : -1
|
||||
Api.moment.like({ dynamic_id: momentId })
|
||||
.catch(() => { m.isLiked = !willLike; m.likes += willLike ? -1 : 1 })
|
||||
}
|
||||
|
||||
// 评论动态
|
||||
const onComment = (momentId : number) => {
|
||||
uni.navigateTo({ url: `/pages/moment/details?id=${momentId}` })
|
||||
}
|
||||
|
||||
// 分享动态
|
||||
const onShare = (momentId : number) => {
|
||||
console.log('分享动态:', momentId)
|
||||
uni.showActionSheet({
|
||||
itemList: ['分享给好友', '分享到朋友圈', '复制链接', '保存图片'],
|
||||
success: (res) => {
|
||||
console.log('选择了分享方式:', res.tapIndex)
|
||||
uni.showToast({
|
||||
title: '分享成功',
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 更多操作
|
||||
const onMore = (momentId : number) => {
|
||||
console.log('更多操作:', momentId)
|
||||
uni.showActionSheet({
|
||||
itemList: ['举报', '不感兴趣', '屏蔽用户', '保存到相册'],
|
||||
success: (res) => {
|
||||
const actions = ['report', 'notInterested', 'block', 'save']
|
||||
const action = actions[res.tapIndex]
|
||||
console.log('选择了:', action)
|
||||
|
||||
if (action === 'report') {
|
||||
uni.navigateTo({
|
||||
url: '/pages/report/report?momentId=' + momentId
|
||||
})
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '操作成功',
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
const onRefresh = () => {
|
||||
refreshing.value = true
|
||||
page.value = 1
|
||||
loadData(true)
|
||||
setTimeout(() => { refreshing.value = false }, 600)
|
||||
}
|
||||
|
||||
// 上拉加载更多
|
||||
const onLoadMore = () => {
|
||||
if (loading.value || noMore.value) return
|
||||
loadData(false)
|
||||
}
|
||||
|
||||
// 发现好友
|
||||
const findFriends = () => {
|
||||
console.log('去发现好友')
|
||||
uni.switchTab({
|
||||
url: '/pages/home/home'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 50%;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
/* 分类标签 */
|
||||
.category-tabs {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
padding: 20rpx 0;
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin: 0 30rpx;
|
||||
padding: 10rpx 0;
|
||||
}
|
||||
|
||||
.tab-text {
|
||||
font-size: 30rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.tabactive {
|
||||
color: #FF6B6B;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.badge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 32rpx;
|
||||
text-align: center;
|
||||
background-color: #FF6B6B;
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
border-radius: 16rpx;
|
||||
transform: translate(50%, -50%);
|
||||
}
|
||||
|
||||
/* 动态列表 */
|
||||
.moment-list {
|
||||
flex: 1;
|
||||
padding: 0 20rpx 20rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
padding: 20rpx 0 10rpx 20rpx;
|
||||
}
|
||||
|
||||
.pinned-section .section-title {
|
||||
color: #FF9500;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 0;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
margin: 20rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin: 20rpx 0 30rpx;
|
||||
}
|
||||
|
||||
.empty-btn {
|
||||
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%);
|
||||
color: #fff;
|
||||
padding: 20rpx 50rpx;
|
||||
border-radius: 40rpx;
|
||||
font-size: 28rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.empty-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 附近动态头部 */
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx 0 10rpx 20rpx;
|
||||
}
|
||||
|
||||
.section-subtitle {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
/* 热门标签 */
|
||||
.hot-tags {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
padding: 10rpx;
|
||||
}
|
||||
|
||||
.hot-tag {
|
||||
padding: 10rpx;
|
||||
background-color: #f8f8f8;
|
||||
border-radius: 30rpx;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.hot-tag.active {
|
||||
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.load-status {
|
||||
padding: 30rpx 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 发布按钮 */
|
||||
.publish-fab {
|
||||
position: fixed;
|
||||
right: 40rpx;
|
||||
bottom: 140rpx;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
background: linear-gradient(to bottom right , #FF6B6B , #FF8E53);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-shadow: 0 10rpx 30rpx rgba(255, 107, 107, 0.3);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,655 @@
|
||||
// pages/dynamic/publish.uts
|
||||
<template>
|
||||
<view class="page">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="page-nav">
|
||||
<view class="page-nav-left" @click="handleCancel">
|
||||
<text class="cancel-text">取消</text>
|
||||
</view>
|
||||
<view class="page-nav-center">
|
||||
<text class="nav-title">发布动态</text>
|
||||
</view>
|
||||
<view class="page-nav-right">
|
||||
<button class="publish-btn" :class="{ active: canPublish }" @click="handlePublish">发布</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 内容输入区 -->
|
||||
<view class="content-area">
|
||||
<textarea class="content-input" placeholder="记录这一刻,晒给懂你的人" v-model="content" maxlength="250"
|
||||
@input="updateCharCount"></textarea>
|
||||
<text class="char-count">{{ charCount }}/250</text>
|
||||
</view>
|
||||
<!-- 图片上传区 -->
|
||||
<view class="media-area">
|
||||
<view class="upload-grid">
|
||||
<view v-for="(file, index) in files" :key="index" class="grid-item">
|
||||
<image :src="file.path" class="grid-image" mode="aspectFill" />
|
||||
<view class="delete-btn" @click="removeImage(index)">
|
||||
<uni-icons type="closeempty" color="#fff"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="files.length < 9" class="grid-item add-item" @click="chooseMedia">
|
||||
<!-- <view class="add-icon">+</view> -->
|
||||
<uni-icons type="plusempty" size="48"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 功能选项区 -->
|
||||
<view class="function-area">
|
||||
<view class="function-item" @click="chooseLocation">
|
||||
<view class="item-left">
|
||||
<image src="/static/icons/location.png" class="item-icon" />
|
||||
<text class="item-label">所在位置</text>
|
||||
</view>
|
||||
<view class="item-right">
|
||||
<text class="item-value">{{ location ?? '未选择' }}</text>
|
||||
<image src="/static/icons/arrow-right.png" class="arrow-icon" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="function-item" @click="showTopicDialog">
|
||||
<view class="item-left">
|
||||
<image src="/static/icons/topic.png" class="item-icon" />
|
||||
<text class="item-label">添加话题</text>
|
||||
</view>
|
||||
<view class="item-right">
|
||||
<text class="item-value">{{ topics.length > 0 ? `#${topics.join(' #')}` : '选择话题会获得更多互动哦' }}</text>
|
||||
<image src="/static/icons/arrow-right.png" class="arrow-icon" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="function-item voice-item" @click="toggleRecording">
|
||||
<view class="item-left">
|
||||
<image :src="isRecording ? '/static/icons/voice-recording.png' : '/static/icons/voice.png'"
|
||||
class="item-icon" />
|
||||
<text class="item-label">{{ isRecording ? '正在录音...' : '语音描述' }}</text>
|
||||
</view>
|
||||
<view class="item-right">
|
||||
<view v-if="audioPath" class="audio-preview">
|
||||
<image src="/static/icons/play.png" class="play-icon" @click.stop="playAudio" />
|
||||
<text class="audio-duration">{{ audioDuration }}"</text>
|
||||
</view>
|
||||
<image v-else src="/static/icons/arrow-right.png" class="arrow-icon" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 话题选择弹窗 -->
|
||||
<view v-if="showTopicSelector" class="topic-dialog">
|
||||
<view class="dialog-mask" @click="hideTopicDialog"></view>
|
||||
<view class="dialog-content">
|
||||
<view class="dialog-header">
|
||||
<text class="dialog-title">选择话题</text>
|
||||
<text class="dialog-close" @click="hideTopicDialog">×</text>
|
||||
</view>
|
||||
<view class="topic-list">
|
||||
<view v-for="(topic, index) in availableTopics" :key="index" class="topic-item"
|
||||
@click="toggleTopic(topic)">
|
||||
<text class="topic-text">#{{ topic }}</text>
|
||||
<view class="topic-check" v-if="topics.includes(topic)">
|
||||
<image src="/static/icons/checked.png" class="check-icon" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="dialog-footer">
|
||||
<button class="confirm-btn" @click="hideTopicDialog">完成</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 位置选择弹窗 -->
|
||||
<view v-if="showLocationSelector" class="location-dialog">
|
||||
<view class="dialog-mask" @click="hideLocationDialog"></view>
|
||||
<view class="dialog-content">
|
||||
<view class="dialog-header">
|
||||
<text class="dialog-title">选择位置</text>
|
||||
<text class="dialog-close" @click="hideLocationDialog">×</text>
|
||||
</view>
|
||||
<view class="search-box">
|
||||
<input v-model="locationSearch" placeholder="搜索地点" class="search-input" />
|
||||
</view>
|
||||
<view class="location-list">
|
||||
<view v-for="(loc, index) in filteredLocations" :key="index" class="location-item"
|
||||
@click="selectLocation(loc)">
|
||||
<text class="location-name">{{ loc.name }}</text>
|
||||
<text class="location-address">{{ loc.address }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import httpApi, { type IFileInfo } from '@/utlis/http-api'
|
||||
import type { IUploadOption } from '@/types/http.uts'
|
||||
import Api from '@/common/api-service.uts'
|
||||
// 响应式数据
|
||||
type locationItem = { name : string, address : string }
|
||||
const content = ref<string>('')
|
||||
const charCount = ref<number>(0)
|
||||
const location = ref<string>('')
|
||||
const topics = ref<Array<string>>([])
|
||||
const audioPath = ref<string>('')
|
||||
const audioDuration = ref<number>(0)
|
||||
const isRecording = ref<boolean>(false)
|
||||
const showTopicSelector = ref<boolean>(false)
|
||||
const showLocationSelector = ref<boolean>(false)
|
||||
const locationSearch = ref<string>('')
|
||||
const recorderManager = ref(null as RecorderManager | null)
|
||||
const files = ref<IFileInfo[]>([])
|
||||
// 可用话题列表
|
||||
const availableTopics = ref<Array<string>>([
|
||||
'美食分享', '厨房日记', '烹饪技巧', '家常菜', '烘焙时光',
|
||||
'美食探店', '食材百科', '健康饮食', '快手菜', '创意料理'
|
||||
])
|
||||
|
||||
// 位置列表
|
||||
const locations = ref<Array<locationItem>>([
|
||||
{ name: '北京市朝阳区三里屯', address: '北京市朝阳区三里屯街道' },
|
||||
{ name: '上海市黄浦区外滩', address: '上海市黄浦区外滩街道' },
|
||||
{ name: '广州市天河区珠江新城', address: '广州市天河区珠江新城' },
|
||||
{ name: '深圳市南山区科技园', address: '深圳市南山区科技园' },
|
||||
{ name: '成都市锦江区春熙路', address: '成都市锦江区春熙路' }
|
||||
])
|
||||
|
||||
// 计算属性
|
||||
const canPublish = computed(() => {
|
||||
return content.value.trim().length > 0 || files.value.length > 0 || audioPath.value !== ''
|
||||
})
|
||||
|
||||
const filteredLocations = computed<locationItem[]>(() => {
|
||||
if (locationSearch.value != null) return locations.value
|
||||
return locations.value.filter(loc =>
|
||||
loc.name.includes(locationSearch.value) || loc.address.includes(locationSearch.value)
|
||||
)
|
||||
})
|
||||
|
||||
// 生命周期钩子
|
||||
onLoad(() => {
|
||||
// 页面加载时的初始化操作
|
||||
//updateCharCount()
|
||||
})
|
||||
|
||||
// 方法定义
|
||||
function updateCharCount() {
|
||||
charCount.value = content.value.length
|
||||
}
|
||||
|
||||
function chooseMedia() {
|
||||
// #ifndef WEB
|
||||
uni.chooseMedia({
|
||||
count: 9 - files.value.length,
|
||||
mediaType: ['image', 'video'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: (res) => {
|
||||
console.log(res)
|
||||
res.tempFiles.forEach((path : ChooseMediaTempFile) => {
|
||||
files.value.push({
|
||||
index: files.value.length ,
|
||||
name: 'file' + (files.value.length ),
|
||||
path: path.tempFilePath,
|
||||
} as IFileInfo)
|
||||
})
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
// #ifdef WEB
|
||||
uni.chooseImage({
|
||||
count: 9 - files.value.length,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: (res) => {
|
||||
console.log(res)
|
||||
res.tempFilePaths.forEach((file) => {
|
||||
files.value.push({
|
||||
index: files.value.length ,
|
||||
name: 'file' + (files.value.length ),
|
||||
path: file,
|
||||
} )
|
||||
})
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
|
||||
function removeImage(index : number) {
|
||||
files.value.splice(index, 1)
|
||||
}
|
||||
|
||||
function chooseLocation() {
|
||||
showLocationSelector.value = true
|
||||
}
|
||||
function showTopicDialog() {
|
||||
showTopicSelector.value = true
|
||||
}
|
||||
|
||||
function hideTopicDialog() {
|
||||
showTopicSelector.value = false
|
||||
}
|
||||
|
||||
function hideLocationDialog() {
|
||||
showLocationSelector.value = false
|
||||
locationSearch.value = ''
|
||||
}
|
||||
|
||||
function selectLocation(loc : locationItem) {
|
||||
location.value = loc.name
|
||||
hideLocationDialog()
|
||||
}
|
||||
|
||||
function toggleTopic(topic : string) {
|
||||
const index = topics.value.indexOf(topic)
|
||||
if (index > -1) {
|
||||
topics.value.splice(index, 1)
|
||||
} else {
|
||||
topics.value.push(topic)
|
||||
}
|
||||
}
|
||||
let innerAudioContext : any | null = null
|
||||
function startRecording() {
|
||||
isRecording.value = true
|
||||
audioPath.value = ''
|
||||
audioDuration.value = 0
|
||||
// 创建录音管理器
|
||||
recorderManager.value = uni.getRecorderManager();
|
||||
|
||||
// 录音结束事件
|
||||
recorderManager.value!.onStop((res) => {
|
||||
console.log(res);
|
||||
// isRecording.value = false
|
||||
// audioPath.value = res.tempFilePath
|
||||
// audioDuration.value = Math.floor(res.duration as number / 1000) // 转换为秒
|
||||
})
|
||||
|
||||
// 开始录音
|
||||
recorderManager.value!.start({
|
||||
duration: 60000, // 最长60秒
|
||||
sampleRate: 44100,
|
||||
numberOfChannels: 1,
|
||||
encodeBitRate: 192000,
|
||||
format: 'mp3'
|
||||
})
|
||||
}
|
||||
|
||||
function stopRecording() {
|
||||
if (recorderManager != null) {
|
||||
recorderManager.value!.stop()
|
||||
}
|
||||
}
|
||||
|
||||
function toggleRecording() {
|
||||
if (isRecording.value) {
|
||||
stopRecording()
|
||||
} else {
|
||||
startRecording()
|
||||
}
|
||||
}
|
||||
function playAudio() {
|
||||
// if (!innerAudioContext) {
|
||||
// innerAudioContext = uni.createInnerAudioContext()
|
||||
// }
|
||||
|
||||
// if (innerAudioContext.paused) {
|
||||
// innerAudioContext.src = audioPath.value
|
||||
// innerAudioContext.play()
|
||||
// } else {
|
||||
// innerAudioContext.pause()
|
||||
// }
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
uni.navigateBack()
|
||||
}
|
||||
async function handlePublish() {
|
||||
if (!canPublish.value) return
|
||||
uni.showLoading({ title: '发布中...', mask: true })
|
||||
try {
|
||||
// 1. 上传图片/视频,拿到可访问 URL
|
||||
let media : UTSJSONObject[] = []
|
||||
let hasVideo = false
|
||||
if (files.value.length > 0) {
|
||||
const uploaded = await httpApi.uploadMultipleFilesParallel({
|
||||
url: '/wxchat/api/moment/upload'
|
||||
} as IUploadOption, files.value)
|
||||
uploaded.forEach((f) => {
|
||||
const p = f.path
|
||||
const isVideo = p.endsWith('.mp4') || p.endsWith('.mov') || p.endsWith('.m4v') || p.endsWith('.avi')
|
||||
if (isVideo) hasVideo = true
|
||||
media.push({ type: isVideo ? 2 : 1, url: p } as UTSJSONObject)
|
||||
})
|
||||
}
|
||||
// 2. 计算动态类型:1 文字 / 2 图片 / 3 视频
|
||||
let type = 1
|
||||
if (hasVideo) {
|
||||
type = 3
|
||||
} else if (media.length > 0) {
|
||||
type = 2
|
||||
}
|
||||
// 3. 提交发布
|
||||
await Api.moment.create({
|
||||
content: content.value.trim(),
|
||||
type: type,
|
||||
is_public: 1,
|
||||
location: location.value,
|
||||
media: media,
|
||||
hashtags: topics.value
|
||||
})
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '发布成功', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1200)
|
||||
} catch (error) {
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '发布失败', icon: 'none' })
|
||||
console.log('发布失败:', error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/* 内容输入区 */
|
||||
.content-area {
|
||||
margin-top: 100rpx;
|
||||
position: relative;
|
||||
padding: 16px;
|
||||
background-color: #fff;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.content-input {
|
||||
width: 100%;
|
||||
min-height: 120px;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
color: #333;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.char-count {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
bottom: 8px;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
/* 图片上传区 */
|
||||
.media-area {
|
||||
padding: 16px;
|
||||
background-color: #fff;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.upload-grid {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
border-radius: 25rpx;
|
||||
overflow: hidden;
|
||||
background: none;
|
||||
|
||||
.grid-item {
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
margin: 3rpx;
|
||||
overflow: hidden;
|
||||
background-color: #f5f5f5;
|
||||
flex: 0 0 222rpx;
|
||||
height: 222rpx;
|
||||
background-color: #f5f5f5;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
|
||||
.grid-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
position: absolute;
|
||||
top: 10rpx;
|
||||
right: 10rpx;
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&.add-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px dashed #ccc;
|
||||
|
||||
.add-icon {
|
||||
font-size: 32px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 功能选项区 */
|
||||
.function-area {
|
||||
background-color: #fff;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.function-item {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.item-left {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
align-items: center;
|
||||
|
||||
.item-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.item-label {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.item-right {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
align-items: center;
|
||||
|
||||
.item-value {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-right: 8px;
|
||||
max-width: 200px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.arrow-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.audio-preview {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
align-items: center;
|
||||
|
||||
.play-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.audio-duration {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.voice-item {
|
||||
.item-label {
|
||||
color: #ff2442;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 弹窗样式 */
|
||||
.topic-dialog,
|
||||
.location-dialog {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1000;
|
||||
|
||||
.dialog-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.dialog-content {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: #fff;
|
||||
border-radius: 12px 12px 0 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.dialog-header {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
.dialog-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.dialog-close {
|
||||
font-size: 24px;
|
||||
color: #999;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.topic-list,
|
||||
.location-list {
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
|
||||
.topic-item,
|
||||
.location-item {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.topic-text {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.topic-check {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background-color: #ff2442;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.check-icon {
|
||||
width: 12px;
|
||||
height: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.location-name {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.location-address {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-box {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
padding: 0 12px;
|
||||
border-radius: 18px;
|
||||
background-color: #f5f5f5;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
padding: 16px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
|
||||
.confirm-btn {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
background-color: #ff2442;
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
border-radius: 22px;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user