- 移除只写不读的 verifyCodeCache(无界内存增长) - 大神收益走势由 20 次逐日/逐周/逐月查询改为 3 次 GROUP BY 聚合 - 派单池地理编码改为并行,避免串行等待外部接口 - 补齐 orders/activities/participants/user_notifications/finance/withdrawals/posts 组合索引;修复 withdrawals 使用不存在 created_at 的失效索引 - 数据库连接池支持 MYSQL_CONNECTION_LIMIT/MYSQL_QUEUE_LIMIT - 首页/弹窗定时器卸载清理,活动列表参与者按活动 id 查询,大厅组局按页取数,图片 lazy-load - 121 套件/792 用例全绿;已部署并验证新索引与 9ms 走势查询
3490 行
93 KiB
Vue
3490 行
93 KiB
Vue
<template>
|
||
<view class="community-container" :class="{ 'theme-warm': currentThemeKey === 'warm' }" :style="navStyle">
|
||
<!-- 友圈广场骨架屏 (首屏加载中流光占位,杜绝视觉空白) -->
|
||
<skeleton-screen v-if="isLoading" type="community" />
|
||
|
||
<!-- 真实内容 (数据加载完成平滑展示) -->
|
||
<view v-else class="community-body-wrapper">
|
||
<!-- 顶部状态栏与标题区 (将发动态置于胶囊正下方,规避横向挤压) -->
|
||
<view class="community-header">
|
||
<!-- 第一行:主标题与胶囊水平对齐 -->
|
||
<view class="header-title-row">
|
||
<text class="page-title">友圈广场</text>
|
||
</view>
|
||
<!-- 第二行:副标题与发动态按钮 (位于胶囊正下方) -->
|
||
<view class="header-sub-row">
|
||
<text class="page-sub">同城搭子 · 车队组排 · 游戏高光</text>
|
||
<view class="publish-btn btn-active" @click="handlePublishPost">
|
||
<image class="publish-icon" :src="ICONS.editPen" mode="aspectFit" />
|
||
<text class="publish-text">发动态</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 友圈置顶头条公告 (跑马灯平滑微动与点击查看详情弹窗组合) -->
|
||
<view class="community-notice-bar btn-active" v-if="communityNotice && communityNotice.title" @click="openNoticeDetailModal">
|
||
<view class="notice-badge">{{ communityNotice.tag || '置顶推荐' }}</view>
|
||
<view class="notice-marquee-wrap">
|
||
<view class="notice-marquee-content">
|
||
<text class="notice-title-text">{{ communityNotice.title }}:{{ communityNotice.content }}</text>
|
||
<text class="notice-title-text marquee-clone">{{ communityNotice.title }}:{{ communityNotice.content }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="notice-action-hint">
|
||
<text class="hint-text">详情</text>
|
||
<image class="hint-arrow" :src="ICONS.chevronRight" mode="aspectFit" />
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 分类筛选栏 (高能矢量Tab标签与标准HOT角标) -->
|
||
<view class="feed-tabs-wrap">
|
||
<scroll-view class="feed-scroll" scroll-x :show-scrollbar="false">
|
||
<view class="tabs-flex">
|
||
<view
|
||
v-for="tab in feedTabs"
|
||
:key="tab.key"
|
||
class="tab-pill btn-active"
|
||
:class="{ active: currentTab === tab.key }"
|
||
@click="switchFeedTab(tab.key)"
|
||
>
|
||
<image v-if="tab.icon" class="tab-icon-svg" :src="tab.icon" mode="aspectFit" />
|
||
<text class="pill-text">{{ tab.name }}</text>
|
||
<view class="tab-badge" v-if="tab.hot">
|
||
<text class="badge-text">HOT</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
|
||
<!-- 动态信息流列表 -->
|
||
<view class="posts-list">
|
||
<view v-if="filteredPosts.length === 0" class="posts-empty">
|
||
<text class="posts-empty-title">还没有动态</text>
|
||
<text class="posts-empty-desc">发第一条同城搭子动态,约上附近的小伙伴</text>
|
||
</view>
|
||
<view
|
||
v-for="(post, index) in filteredPosts"
|
||
:key="post.id"
|
||
class="post-card"
|
||
:style="{ animationDelay: `${Math.min(index, 8) * 45}ms` }"
|
||
@click="openPostDetail(post)"
|
||
>
|
||
<!-- 作者信息行 -->
|
||
<view class="author-row">
|
||
<image class="author-avatar btn-active" :src="post.avatar" mode="aspectFill" @click.stop="openAuthorProfile(post)" />
|
||
<view class="author-meta btn-active" @click.stop="openAuthorProfile(post)">
|
||
<view class="name-line">
|
||
<text class="author-name">{{ post.author }}</text>
|
||
<text class="author-tag" :class="post.tagType">{{ post.tag }}</text>
|
||
</view>
|
||
<view class="sub-line">
|
||
<text class="post-time">{{ post.time }}</text>
|
||
<text class="dot-sep">·</text>
|
||
<view class="distance-box">
|
||
<image class="dist-icon" :src="ICONS.mapPin" mode="aspectFit" />
|
||
<text class="dist-text">{{ post.location }} ({{ post.distance }})</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="author-actions">
|
||
<template v-if="isPostOwner(post)">
|
||
<button
|
||
class="join-btn btn-active"
|
||
style="margin-right: 12rpx;"
|
||
@click.stop="handleManageApplies(post)"
|
||
>
|
||
申请({{ post.applyCount || 0 }})
|
||
</button>
|
||
<button
|
||
class="btn-delete-post btn-active"
|
||
@click.stop="handleDeletePost(post)"
|
||
>
|
||
删除
|
||
</button>
|
||
</template>
|
||
<button
|
||
v-else
|
||
class="join-btn btn-active"
|
||
:class="{ 'btn-applied': post.hasApplied && post.myApplyStatus !== 'REJECTED' }"
|
||
:disabled="post.hasApplied && post.myApplyStatus !== 'REJECTED'"
|
||
@click.stop="handleJoinParty(post)"
|
||
>
|
||
{{ post.myApplyStatus === 'ACCEPTED' ? '已同意上车' : (post.myApplyStatus === 'REJECTED' ? '重新申请' : (post.hasApplied ? '待房主确认' : (post.actionText || '聊一聊'))) }}
|
||
</button>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 动态文本内容 -->
|
||
<text class="post-content">{{ post.content }}</text>
|
||
|
||
<!-- 话题标签 -->
|
||
<view class="post-topics" v-if="post.topics && post.topics.length">
|
||
<text v-for="t in post.topics" :key="t" class="topic-item">#{{ t }}</text>
|
||
</view>
|
||
|
||
<!-- 图片九宫格 / 展位 -->
|
||
<view class="post-images" v-if="post.images && post.images.length">
|
||
<image
|
||
v-for="(img, idx) in post.images"
|
||
:key="idx"
|
||
class="post-img"
|
||
lazy-load
|
||
:src="img"
|
||
mode="aspectFill"
|
||
@click.stop="previewImage(post.images, idx)"
|
||
/>
|
||
</view>
|
||
|
||
<!-- 底部互动操作栏 -->
|
||
<view class="post-footer">
|
||
<view class="footer-left">
|
||
<view class="action-item btn-active" @click.stop="toggleLike(post)">
|
||
<image class="action-icon" :src="post.liked ? ICONS.heartFilled : ICONS.heart" mode="aspectFit" />
|
||
<text class="action-count" :class="{ liked: post.liked }">{{ post.likes }}</text>
|
||
</view>
|
||
<view class="action-item btn-active" @click.stop="handleComment(post)">
|
||
<image class="action-icon" :src="ICONS.headphones" mode="aspectFit" />
|
||
<text class="action-count">{{ post.comments }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="footer-right">
|
||
<text class="view-tag">浏览 {{ post.views }} 次</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 发动态弹窗编辑器 (顶奢社交风格 Bottom Sheet,支持手柄/分段器/沉浸式输入/优雅九宫格) -->
|
||
<view class="publish-modal-mask" v-if="showPublishModal" @click="closePublishModal">
|
||
<view class="publish-modal-box" @click.stop="stopPropagation">
|
||
<!-- 抽屉顶部居中下拉把手 -->
|
||
<view class="sheet-drag-handle"></view>
|
||
|
||
<!-- 弹窗顶栏:标题 + 副标题 + 关闭按钮 -->
|
||
<view class="modal-header">
|
||
<view class="header-titles">
|
||
<text class="modal-title">发布友圈动态</text>
|
||
<text class="modal-sub">与附近 3km 的同城同好分享高光时刻</text>
|
||
</view>
|
||
<view class="close-btn btn-active" @click="closePublishModal">
|
||
<image class="close-icon" :src="ICONS.close" mode="aspectFit" />
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 分类分段控制器 (iOS / 现代 App Segmented Control 风格一体化滑块) -->
|
||
<view class="segment-tabs-container">
|
||
<view
|
||
v-for="cat in publishCategories"
|
||
:key="cat.key"
|
||
class="segment-item btn-active"
|
||
:class="{ active: newPostForm.category === cat.key }"
|
||
@click="selectPublishCategory(cat.key)"
|
||
>
|
||
<text class="segment-text">{{ cat.name }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 正文沉浸式输入区 -->
|
||
<view class="textarea-card">
|
||
<textarea
|
||
class="post-textarea"
|
||
v-model="newPostForm.content"
|
||
:placeholder="contentPlaceholder"
|
||
placeholder-class="ph"
|
||
maxlength="300"
|
||
:show-confirm-bar="false"
|
||
/>
|
||
<view class="textarea-corner-meta">
|
||
<view class="char-count-badge" :class="{ 'near-limit': newPostForm.content.length > 270 }">
|
||
<text class="current-len">{{ newPostForm.content.length }}</text>
|
||
<text class="max-len">/300</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 推荐热门话题 (微胶囊流式布局) -->
|
||
<view class="topics-section">
|
||
<view class="section-title-row">
|
||
<text class="title-text">推荐话题</text>
|
||
<text class="title-tip">最多选择3个话题标签</text>
|
||
</view>
|
||
<view class="topics-flex">
|
||
<view
|
||
v-for="topic in availableTopics"
|
||
:key="topic"
|
||
class="topic-chip btn-active"
|
||
:class="{ selected: isTopicSelected(topic) }"
|
||
@click="toggleTopic(topic)"
|
||
>
|
||
<text class="hash-tag">#</text>
|
||
<text class="topic-name">{{ topic }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 图片九宫格选择区 (小红书/朋友圈现代画廊式) -->
|
||
<view class="gallery-section">
|
||
<view class="gallery-grid">
|
||
<view
|
||
class="grid-img-item"
|
||
v-for="(img, idx) in newPostForm.images"
|
||
:key="idx"
|
||
>
|
||
<image class="thumb-img" :src="img" mode="aspectFill" @click="previewImage(newPostForm.images, idx)" />
|
||
<view class="remove-btn btn-active" @click.stop="removeImage(idx)">
|
||
<image class="remove-icon" :src="ICONS.close" mode="aspectFit" />
|
||
</view>
|
||
</view>
|
||
<view
|
||
class="add-img-tile btn-active"
|
||
v-if="newPostForm.images.length < 9"
|
||
@click="choosePostImage"
|
||
>
|
||
<view class="tile-icon-circle">
|
||
<image class="cam-icon" :src="ICONS.camera" mode="aspectFit" />
|
||
</view>
|
||
<text class="tile-label">添加图片</text>
|
||
<text class="tile-count">{{ newPostForm.images.length }}/9</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 定位信息栏 (轻量微胶囊) -->
|
||
<view class="meta-row">
|
||
<view class="location-pill">
|
||
<image class="loc-pin" :src="ICONS.mapPin" mode="aspectFit" />
|
||
<text class="loc-name">{{ newPostForm.location }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部发布操作栏 (双按钮高能对比) -->
|
||
<view class="modal-footer-row">
|
||
<button class="action-btn-cancel btn-active" @click="closePublishModal">取消</button>
|
||
<button
|
||
class="action-btn-submit btn-active"
|
||
:disabled="!newPostForm.content.trim()"
|
||
@click="submitPublishPost"
|
||
>
|
||
立即发布
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 动态互动评论区抽屉组件 -->
|
||
<view class="comment-drawer-mask" v-if="commentDrawerVisible" @click="closeCommentDrawer">
|
||
<view class="comment-drawer-content" @click.stop="stopPropagation">
|
||
<view class="drawer-header">
|
||
<view class="drawer-header-left">
|
||
<text class="drawer-title">动态评论</text>
|
||
<text class="drawer-badge" v-if="activeCommentPost">({{ commentsList.length }})</text>
|
||
</view>
|
||
<view class="drawer-close" @click="closeCommentDrawer">
|
||
<image class="close-icon" :src="ICONS.close" mode="aspectFit" />
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 评论列表区域 -->
|
||
<scroll-view class="comments-scroll" scroll-y="true">
|
||
<view v-if="loadingComments" class="comments-loading">
|
||
<text class="loading-text">正在加载评论...</text>
|
||
</view>
|
||
<view v-else-if="commentsList.length === 0" class="comments-empty">
|
||
<image class="empty-icon" :src="ICONS.messageSquare" mode="aspectFit" />
|
||
<text class="empty-text">暂无搭子留言,快来抢占沙发!</text>
|
||
</view>
|
||
<view v-else class="comments-list">
|
||
<view v-for="item in commentsList" :key="item.id" class="comment-item">
|
||
<image class="comment-avatar btn-active" :src="item.avatar || 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=100'" mode="aspectFill" @click.stop="openAuthorProfile(item)" />
|
||
<view class="comment-body">
|
||
<view class="comment-top">
|
||
<text class="comment-author" @click.stop="openAuthorProfile(item)">{{ item.author }}</text>
|
||
<text class="comment-time">{{ item.createdAt ? '刚刚' : '近期' }}</text>
|
||
</view>
|
||
<text class="comment-content">{{ item.content }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<!-- 底部输入栏 -->
|
||
<view class="comment-input-bar">
|
||
<input
|
||
class="comment-input"
|
||
v-model="commentInput"
|
||
placeholder="友善互动,共创精彩搭子体验..."
|
||
placeholder-class="input-placeholder"
|
||
maxlength="200"
|
||
confirm-type="send"
|
||
@confirm="sendComment"
|
||
/>
|
||
<button
|
||
class="comment-send-btn btn-active"
|
||
:disabled="!commentInput.trim() || sendingComment"
|
||
@click="sendComment"
|
||
>
|
||
发送
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 社区置顶公告与公约详情弹窗 -->
|
||
<view class="notice-modal-mask" v-if="noticeModalVisible" @click="closeNoticeDetailModal">
|
||
<view class="notice-modal-content" @click.stop="stopPropagation">
|
||
<view class="modal-header">
|
||
<view class="modal-header-left">
|
||
<view class="modal-badge">{{ communityNotice.tag || '置顶推荐' }}</view>
|
||
<text class="modal-title">{{ communityNotice.title }}</text>
|
||
</view>
|
||
<view class="modal-close btn-active" @click="closeNoticeDetailModal">
|
||
<image class="close-icon" :src="ICONS.close" mode="aspectFit" />
|
||
</view>
|
||
</view>
|
||
|
||
<scroll-view class="notice-modal-body" scroll-y="true">
|
||
<!-- 公告引导前言 -->
|
||
<view class="notice-lead-card">
|
||
<image class="lead-icon" :src="ICONS.shield" mode="aspectFit" />
|
||
<text class="lead-text">{{ communityNotice.content }}</text>
|
||
</view>
|
||
|
||
<!-- 社区规范条款列表 -->
|
||
<view class="notice-rules-section">
|
||
<view class="rules-header">社区公约与安全守则</view>
|
||
<view class="rule-item">
|
||
<view class="rule-index">01</view>
|
||
<view class="rule-body">
|
||
<text class="rule-title">文明交友与真实组局</text>
|
||
<text class="rule-desc">严禁发布虚假、欺诈、低俗或人身攻击信息,发帖请明确时间、地点及AA分摊规则,共建健康同城氛围。</text>
|
||
</view>
|
||
</view>
|
||
<view class="rule-item">
|
||
<view class="rule-index">02</view>
|
||
<view class="rule-body">
|
||
<text class="rule-title">严禁私域引流与脱网交易</text>
|
||
<text class="rule-desc">动态与评论中严禁出现微信号、手机号、二维码等私下引流信息,脱网交易不受平台资金与安全保障。</text>
|
||
</view>
|
||
</view>
|
||
<view class="rule-item">
|
||
<view class="rule-index">03</view>
|
||
<view class="rule-body">
|
||
<text class="rule-title">全天候官方内容安全审查</text>
|
||
<text class="rule-desc">全平台已接入微信官方文本与图片内容安全识别系统,违规内容秒级拦截,涉嫌严重违规者将实施封号处置。</text>
|
||
</view>
|
||
</view>
|
||
<view class="rule-item">
|
||
<view class="rule-index">04</view>
|
||
<view class="rule-body">
|
||
<text class="rule-title">同城线下公开安全履约</text>
|
||
<text class="rule-desc">线下同城搭子组局倡导在正规商圈、餐馆等公开营业场所进行,现场凭借4位数字核销码签到核销。</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<view class="notice-modal-footer">
|
||
<button class="modal-confirm-btn btn-active" @click="closeNoticeDetailModal">已知晓并遵守公约</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部自定义导航栏 (发单人端:活动中心 / 我要发单 / 友圈 / 个人中心) -->
|
||
<AppTabBar current="community" />
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
||
import { onShow } from '@dcloudio/uni-app';
|
||
import AppTabBar from '@/components/CustomTabBar.vue';
|
||
import SkeletonScreen from '@/components/SkeletonScreen.vue';
|
||
import { ICONS } from '@/common/icons.js';
|
||
import { getCurrentThemeKey, applyThemeToNativeUI } from '@/common/theme.js';
|
||
import { ensureLoginOrRedirect } from '@/common/role.js';
|
||
import { getNavBarInfo } from '@/common/nav.js';
|
||
import { getPosts, createPost, deletePost, likePost, viewPost, getPostComments, addPostComment, applyPostParty, getPostApplies, handlePostApply, uploadImage, getPlatformConfigs, getUserProfile, normalizeUserId } from '@/common/api.js';
|
||
import { useShare } from '@/common/share.js';
|
||
|
||
useShare({
|
||
title: '友圈广场 · 发现同城精彩动态与生活日常',
|
||
timelineTitle: '友圈广场 · 看看同城小伙伴的精彩日常',
|
||
path: '/pages/community/index'
|
||
});
|
||
|
||
const DEFAULT_USER_AVATAR = 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=120';
|
||
const isLoading = ref(true);
|
||
const currentThemeKey = ref(getCurrentThemeKey());
|
||
const navInfo = ref(getNavBarInfo());
|
||
const currentUserProfile = ref({});
|
||
|
||
const readCachedUserInfo = () => {
|
||
const pwInfo = uni.getStorageSync('PW_USER_INFO') || {};
|
||
const uInfo = uni.getStorageSync('userInfo') || {};
|
||
const uid = normalizeUserId();
|
||
const rawName = pwInfo.nickname || uInfo.nickname || '';
|
||
const nickname = (rawName && !['点击登录 / 注册', '电竞玩家 (我)'].includes(rawName)) ? rawName : '';
|
||
const avatar = pwInfo.avatar || uInfo.avatar || '';
|
||
return {
|
||
id: uid,
|
||
nickname,
|
||
avatar
|
||
};
|
||
};
|
||
|
||
const syncCurrentUserProfile = async () => {
|
||
const cached = readCachedUserInfo();
|
||
currentUserProfile.value = {
|
||
...currentUserProfile.value,
|
||
...cached
|
||
};
|
||
const storedUid = Number(uni.getStorageSync('userId'));
|
||
if (!storedUid) return currentUserProfile.value;
|
||
try {
|
||
const remote = await getUserProfile(storedUid);
|
||
if (remote && remote.nickname) {
|
||
const merged = {
|
||
...cached,
|
||
...remote,
|
||
nickname: remote.nickname,
|
||
avatar: remote.avatar || cached.avatar || DEFAULT_USER_AVATAR
|
||
};
|
||
currentUserProfile.value = merged;
|
||
uni.setStorageSync('PW_USER_INFO', merged);
|
||
uni.setStorageSync('userInfo', merged);
|
||
}
|
||
} catch (_) {}
|
||
return currentUserProfile.value;
|
||
};
|
||
|
||
const communityNotice = ref({
|
||
title: '友圈同城互动与公约置顶',
|
||
content: '欢迎来到同城搭子友圈!文明交友,发布真实组局诉求;平台全天候对动态与评论实施微信官方内容安全过滤,严禁广告引流及私下交易。',
|
||
tag: '置顶推荐',
|
||
showInBanner: true
|
||
});
|
||
const noticeModalVisible = ref(false);
|
||
|
||
const openNoticeDetailModal = () => {
|
||
noticeModalVisible.value = true;
|
||
};
|
||
|
||
const closeNoticeDetailModal = () => {
|
||
noticeModalVisible.value = false;
|
||
};
|
||
const availableTopics = ref([
|
||
'三里屯探店',
|
||
'铜锅涮肉AA',
|
||
'国服上分车队',
|
||
'饭搭子招募',
|
||
'工体纯K麦霸',
|
||
'同城搭子招募'
|
||
]);
|
||
|
||
const navStyle = computed(() => {
|
||
return {
|
||
'--safe-header-gap': `${navInfo.value.safeHeaderGap}px`,
|
||
'--capsule-right-space': `${navInfo.value.capsuleRightSpace}px`,
|
||
'--capsule-height': `${navInfo.value.capsuleHeight}px`
|
||
};
|
||
});
|
||
|
||
const onThemeChanged = (newTheme) => {
|
||
currentThemeKey.value = newTheme;
|
||
applyThemeToNativeUI(newTheme);
|
||
};
|
||
|
||
const loadInitialCommunity = async () => {
|
||
try {
|
||
await syncCurrentUserProfile();
|
||
await loadRemotePosts();
|
||
} finally {
|
||
setTimeout(() => {
|
||
isLoading.value = false;
|
||
}, 200);
|
||
}
|
||
};
|
||
|
||
onMounted(() => {
|
||
navInfo.value = getNavBarInfo();
|
||
currentThemeKey.value = getCurrentThemeKey();
|
||
applyThemeToNativeUI(currentThemeKey.value);
|
||
uni.$on('theme-change', onThemeChanged);
|
||
loadInitialCommunity();
|
||
loadPlatformCommunityConfigs();
|
||
});
|
||
|
||
onShow(() => {
|
||
navInfo.value = getNavBarInfo();
|
||
currentThemeKey.value = getCurrentThemeKey();
|
||
applyThemeToNativeUI(currentThemeKey.value);
|
||
uni.hideTabBar({ animation: false, fail: () => {} });
|
||
const cachedActive = uni.getStorageSync('ACTIVE_COMMUNITY_POST');
|
||
if (cachedActive && cachedActive.id) {
|
||
const idx = posts.value.findIndex(p => String(p.id) === String(cachedActive.id));
|
||
if (idx > -1) {
|
||
posts.value[idx] = {
|
||
...posts.value[idx],
|
||
...cachedActive
|
||
};
|
||
}
|
||
}
|
||
loadRemotePosts();
|
||
loadPlatformCommunityConfigs();
|
||
});
|
||
|
||
onUnmounted(() => {
|
||
uni.$off('theme-change', onThemeChanged);
|
||
});
|
||
|
||
const loadRemotePosts = async () => {
|
||
try {
|
||
const list = await getPosts(currentTab.value);
|
||
{
|
||
let localLikedIds = uni.getStorageSync('COMMUNITY_LIKED_POST_IDS') || [];
|
||
if (!Array.isArray(localLikedIds)) localLikedIds = [];
|
||
let localAppliedIds = uni.getStorageSync('COMMUNITY_APPLIED_POST_IDS') || [];
|
||
if (!Array.isArray(localAppliedIds)) localAppliedIds = [];
|
||
let viewsMap = uni.getStorageSync('COMMUNITY_POST_VIEWS_MAP') || {};
|
||
if (typeof viewsMap !== 'object' || viewsMap === null) viewsMap = {};
|
||
|
||
posts.value = (list || []).map(item => {
|
||
const strId = String(item.id);
|
||
const serverStatus = item.myApplyStatus || null;
|
||
const isApplied = serverStatus ? (serverStatus !== 'REJECTED') : localAppliedIds.includes(strId);
|
||
const mergedViews = Math.max(Number(item.views) || 1, Number(viewsMap[strId]) || 0);
|
||
return {
|
||
...item,
|
||
views: mergedViews,
|
||
myApplyStatus: serverStatus || (isApplied ? 'PENDING' : null),
|
||
liked: item.liked !== undefined ? item.liked : localLikedIds.includes(strId),
|
||
hasApplied: isApplied,
|
||
actionText: serverStatus === 'ACCEPTED' ? '已同意上车' : (isApplied ? '待房主确认' : (item.actionText || '加入车队'))
|
||
};
|
||
});
|
||
}
|
||
} catch (err) {
|
||
console.warn('[Community] 获取动态列表失败,保持本地列表:', err.message);
|
||
}
|
||
};
|
||
|
||
const loadPlatformCommunityConfigs = async () => {
|
||
try {
|
||
const configs = await getPlatformConfigs();
|
||
if (configs && typeof configs === 'object') {
|
||
if (configs.community_notice && configs.community_notice.title) {
|
||
communityNotice.value = configs.community_notice;
|
||
}
|
||
if (Array.isArray(configs.community_topics) && configs.community_topics.length > 0) {
|
||
availableTopics.value = configs.community_topics.map((t) => t.name || t);
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.warn('[Community] 获取平台话题公告异常降级:', e);
|
||
}
|
||
};
|
||
|
||
|
||
const feedTabs = ref([
|
||
{ key: 'all', name: '全部推荐', icon: ICONS.gameAll, hot: false },
|
||
{ key: 'partner', name: '同城搭子', icon: ICONS.teamUp, hot: true },
|
||
{ key: 'game', name: '车队开黑', icon: ICONS.gameCrosshair, hot: false },
|
||
{ key: 'highlight', name: '战绩高光', icon: ICONS.gameTrophy, hot: false }
|
||
]);
|
||
|
||
const currentTab = ref('all');
|
||
|
||
const syncInitialPostStates = (rawPosts) => {
|
||
try {
|
||
let localLikedIds = uni.getStorageSync('COMMUNITY_LIKED_POST_IDS') || [];
|
||
if (!Array.isArray(localLikedIds)) localLikedIds = [];
|
||
let localAppliedIds = uni.getStorageSync('COMMUNITY_APPLIED_POST_IDS') || [];
|
||
if (!Array.isArray(localAppliedIds)) localAppliedIds = [];
|
||
let viewsMap = uni.getStorageSync('COMMUNITY_POST_VIEWS_MAP') || {};
|
||
if (typeof viewsMap !== 'object' || viewsMap === null) viewsMap = {};
|
||
|
||
return rawPosts.map(p => {
|
||
const strId = String(p.id);
|
||
const isApplied = localAppliedIds.includes(strId);
|
||
const mergedViews = Math.max(Number(p.views) || 1, Number(viewsMap[strId]) || 0);
|
||
return {
|
||
...p,
|
||
views: mergedViews,
|
||
liked: localLikedIds.includes(strId) ? true : p.liked,
|
||
hasApplied: isApplied,
|
||
actionText: isApplied ? '已申请' : p.actionText
|
||
};
|
||
});
|
||
} catch (_) {
|
||
return rawPosts;
|
||
}
|
||
};
|
||
const syncInitialLikedState = syncInitialPostStates;
|
||
|
||
const posts = ref([]);
|
||
|
||
|
||
const filteredPosts = computed(() => {
|
||
if (currentTab.value === 'all') return posts.value;
|
||
return posts.value.filter(p => p.category === currentTab.value);
|
||
});
|
||
|
||
const switchFeedTab = (key) => {
|
||
currentTab.value = key;
|
||
};
|
||
|
||
const toggleLike = async (post) => {
|
||
if (!ensureLoginOrRedirect('/pages/community/index')) {
|
||
return;
|
||
}
|
||
const willLike = !post.liked;
|
||
post.liked = willLike;
|
||
post.likes += willLike ? 1 : -1;
|
||
|
||
// 1. 同步更新本地存储的点赞记录 (保障切页、断网或刷新时不丢失状态)
|
||
let localLikedIds = uni.getStorageSync('COMMUNITY_LIKED_POST_IDS') || [];
|
||
if (!Array.isArray(localLikedIds)) localLikedIds = [];
|
||
const strId = String(post.id);
|
||
if (willLike) {
|
||
if (!localLikedIds.includes(strId)) localLikedIds.push(strId);
|
||
} else {
|
||
localLikedIds = localLikedIds.filter(id => id !== strId);
|
||
}
|
||
uni.setStorageSync('COMMUNITY_LIKED_POST_IDS', localLikedIds);
|
||
|
||
uni.showToast({
|
||
title: willLike ? '已点赞' : '已取消点赞',
|
||
icon: 'none',
|
||
duration: 1000
|
||
});
|
||
|
||
if (post.id) {
|
||
try {
|
||
const res = await likePost(post.id, willLike ? 'like' : 'unlike');
|
||
if (res && res.likes !== undefined) {
|
||
post.likes = res.likes;
|
||
}
|
||
if (res && res.liked !== undefined) {
|
||
post.liked = res.liked;
|
||
if (res.liked && !localLikedIds.includes(strId)) {
|
||
localLikedIds.push(strId);
|
||
uni.setStorageSync('COMMUNITY_LIKED_POST_IDS', localLikedIds);
|
||
} else if (!res.liked && localLikedIds.includes(strId)) {
|
||
localLikedIds = localLikedIds.filter(id => id !== strId);
|
||
uni.setStorageSync('COMMUNITY_LIKED_POST_IDS', localLikedIds);
|
||
}
|
||
}
|
||
} catch (err) {
|
||
// 回滚乐观更新,避免"已点赞"却未落盘
|
||
console.warn('[Community] 点赞落盘异常:', err.message);
|
||
post.liked = !willLike;
|
||
post.likes += willLike ? -1 : 1;
|
||
let revertIds = uni.getStorageSync('COMMUNITY_LIKED_POST_IDS') || [];
|
||
if (!Array.isArray(revertIds)) revertIds = [];
|
||
if (willLike) {
|
||
revertIds = revertIds.filter(id => id !== strId);
|
||
} else if (!revertIds.includes(strId)) {
|
||
revertIds.push(strId);
|
||
}
|
||
uni.setStorageSync('COMMUNITY_LIKED_POST_IDS', revertIds);
|
||
uni.showToast({ title: (err && err.message) || '操作失败,请重试', icon: 'none' });
|
||
}
|
||
}
|
||
};
|
||
|
||
// 评论区抽屉与动态详情跳转
|
||
const commentDrawerVisible = ref(false);
|
||
const activeCommentPost = ref(null);
|
||
const commentsList = ref([]);
|
||
const commentInput = ref('');
|
||
const loadingComments = ref(false);
|
||
const sendingComment = ref(false);
|
||
|
||
const loadPostCommentsList = async (post) => {
|
||
if (!post) return;
|
||
loadingComments.value = true;
|
||
try {
|
||
const list = await getPostComments(post.id);
|
||
commentsList.value = Array.isArray(list) ? list : [];
|
||
} catch (_) {
|
||
commentsList.value = [];
|
||
} finally {
|
||
loadingComments.value = false;
|
||
}
|
||
};
|
||
|
||
const openAuthorProfile = (target) => {
|
||
if (!target) return;
|
||
const authorName = target.author || target.nickname || '同城达人';
|
||
let targetId = target.expertId || target.companionId || target.userId;
|
||
if (!targetId) {
|
||
uni.showToast({ title: '该用户信息不完整', icon: 'none' });
|
||
return;
|
||
}
|
||
const authorSnapshot = {
|
||
id: targetId,
|
||
userId: target.userId || targetId,
|
||
nickname: authorName,
|
||
avatar: target.avatar || DEFAULT_USER_AVATAR,
|
||
rankTag: target.tag || '同城认证达人',
|
||
levelTag: '实名认证',
|
||
game: target.category === 'game' ? '车队开黑' : (target.category === 'highlight' ? '战绩高光' : '同城搭子'),
|
||
city: target.location || '',
|
||
bio: target.content || '',
|
||
photos: Array.isArray(target.images) && target.images.length > 0 ? target.images : [target.avatar || DEFAULT_USER_AVATAR]
|
||
};
|
||
uni.setStorageSync('ACTIVE_AUTHOR_PROFILE', authorSnapshot);
|
||
const qName = encodeURIComponent(authorSnapshot.nickname);
|
||
const qAvatar = encodeURIComponent(authorSnapshot.avatar);
|
||
const qTag = encodeURIComponent(authorSnapshot.rankTag);
|
||
const qCity = encodeURIComponent(authorSnapshot.city);
|
||
uni.navigateTo({
|
||
url: `/pages/expert/detail?id=${encodeURIComponent(String(targetId))}&name=${qName}&avatar=${qAvatar}&tag=${qTag}&city=${qCity}`
|
||
});
|
||
};
|
||
|
||
const openPostDetail = (post) => {
|
||
if (!post) return;
|
||
const strId = String(post.id);
|
||
const nextViews = (Number(post.views) || 0) + 1;
|
||
post.views = nextViews;
|
||
let viewsMap = uni.getStorageSync('COMMUNITY_POST_VIEWS_MAP') || {};
|
||
if (typeof viewsMap !== 'object' || viewsMap === null) viewsMap = {};
|
||
viewsMap[strId] = Math.max(Number(viewsMap[strId] || 0), nextViews);
|
||
uni.setStorageSync('COMMUNITY_POST_VIEWS_MAP', viewsMap);
|
||
uni.setStorageSync('ACTIVE_COMMUNITY_POST', { ...post, views: viewsMap[strId] });
|
||
viewPost(post.id).then((res) => {
|
||
if (res && res.views !== undefined) {
|
||
post.views = Math.max(Number(post.views) || 0, Number(res.views));
|
||
}
|
||
}).catch(() => {});
|
||
uni.navigateTo({
|
||
url: `/pages/community/detail?id=${post.id}`
|
||
});
|
||
};
|
||
|
||
const handleComment = async (post) => {
|
||
if (!ensureLoginOrRedirect('/pages/community/index')) {
|
||
return;
|
||
}
|
||
activeCommentPost.value = post;
|
||
commentDrawerVisible.value = true;
|
||
commentInput.value = '';
|
||
await loadPostCommentsList(post);
|
||
};
|
||
|
||
const closeCommentDrawer = () => {
|
||
commentDrawerVisible.value = false;
|
||
activeCommentPost.value = null;
|
||
commentsList.value = [];
|
||
commentInput.value = '';
|
||
};
|
||
|
||
const sendComment = async () => {
|
||
if (!ensureLoginOrRedirect('/pages/community/index')) {
|
||
return;
|
||
}
|
||
if (!commentInput.value.trim()) {
|
||
uni.showToast({ title: '请输入评论内容', icon: 'none' });
|
||
return;
|
||
}
|
||
if (!activeCommentPost.value) return;
|
||
|
||
sendingComment.value = true;
|
||
const content = commentInput.value.trim();
|
||
const profile = currentUserProfile.value.nickname
|
||
? currentUserProfile.value
|
||
: await syncCurrentUserProfile();
|
||
const uid = normalizeUserId();
|
||
const authorName = profile.nickname || '同城用户';
|
||
const authorAvatar = profile.avatar || DEFAULT_USER_AVATAR;
|
||
|
||
try {
|
||
const res = await addPostComment(activeCommentPost.value.id, {
|
||
userId: uid,
|
||
author: authorName,
|
||
avatar: authorAvatar,
|
||
content
|
||
});
|
||
|
||
commentsList.value.push({
|
||
id: res ? res.id : `cmt_${Date.now()}`,
|
||
userId: uid,
|
||
author: (res && res.author) || authorName,
|
||
avatar: (res && res.avatar) || authorAvatar,
|
||
content,
|
||
createdAt: '刚刚'
|
||
});
|
||
|
||
if (activeCommentPost.value.comments !== undefined) {
|
||
activeCommentPost.value.comments += 1;
|
||
}
|
||
commentInput.value = '';
|
||
uni.showToast({ title: '评论发表成功', icon: 'success' });
|
||
} catch (err) {
|
||
uni.showToast({ title: err.message || '发表失败,请重试', icon: 'none' });
|
||
} finally {
|
||
sendingComment.value = false;
|
||
}
|
||
};
|
||
|
||
const isPostOwner = (post) => {
|
||
const cur = currentUserProfile.value.nickname ? currentUserProfile.value : readCachedUserInfo();
|
||
return post && (
|
||
Number(post.userId) === Number(cur.id || 0) ||
|
||
post.author === '电竞玩家 (我)' ||
|
||
(cur.nickname && post.author === cur.nickname) ||
|
||
post.isMine === true
|
||
);
|
||
};
|
||
|
||
const handleDeletePost = (post) => {
|
||
if (!ensureLoginOrRedirect('/pages/community/index')) {
|
||
return;
|
||
}
|
||
uni.showModal({
|
||
title: '删除动态确认',
|
||
content: '确定要删除这条动态吗?删除后将无法恢复。',
|
||
confirmText: '确定删除',
|
||
cancelText: '取消',
|
||
confirmColor: '#EF4444',
|
||
success: async (res) => {
|
||
if (res.confirm) {
|
||
try {
|
||
uni.showLoading({ title: '正在删除...' });
|
||
await deletePost(post.id);
|
||
posts.value = posts.value.filter(p => p.id !== post.id);
|
||
uni.hideLoading();
|
||
uni.showToast({ title: '动态已删除', icon: 'success' });
|
||
} catch (err) {
|
||
uni.hideLoading();
|
||
uni.showToast({ title: (err && err.message) || '删除失败,请重试', icon: 'none' });
|
||
}
|
||
}
|
||
}
|
||
});
|
||
};
|
||
|
||
const handleManageApplies = async (post) => {
|
||
if (!ensureLoginOrRedirect('/pages/community/index')) {
|
||
return;
|
||
}
|
||
uni.showLoading({ title: '加载申请列表...' });
|
||
try {
|
||
const list = await getPostApplies(post.id);
|
||
uni.hideLoading();
|
||
if (!list || list.length === 0) {
|
||
uni.showToast({ title: '暂无搭子上车申请', icon: 'none' });
|
||
return;
|
||
}
|
||
const itemList = list.slice(0, 6).map(a => {
|
||
const name = a.applicant_name || a.applicantName || '同城搭子';
|
||
const st = a.status === 'ACCEPTED' ? '已同意' : (a.status === 'REJECTED' ? '已婉拒' : '待确认');
|
||
return `${name} [${st}] - 点击审批`;
|
||
});
|
||
uni.showActionSheet({
|
||
itemList,
|
||
success: (res) => {
|
||
const target = list[res.tapIndex];
|
||
if (!target) return;
|
||
const name = target.applicant_name || target.applicantName || '同城搭子';
|
||
uni.showModal({
|
||
title: `审批搭子申请`,
|
||
content: `申请人:${name}\n请选择是否同意该搭子加入车队/同城局:`,
|
||
confirmText: '同意上车',
|
||
cancelText: '婉拒申请',
|
||
confirmColor: '#10B981',
|
||
success: async (mRes) => {
|
||
const newStatus = mRes.confirm ? 'ACCEPTED' : (mRes.cancel ? 'REJECTED' : null);
|
||
if (!newStatus) return;
|
||
try {
|
||
await handlePostApply(target.id, newStatus);
|
||
uni.showToast({
|
||
title: newStatus === 'ACCEPTED' ? '已同意搭子上车' : '已婉拒该申请',
|
||
icon: 'success'
|
||
});
|
||
loadRemotePosts();
|
||
} catch (err) {
|
||
uni.showToast({ title: err.message || '操作失败', icon: 'none' });
|
||
}
|
||
}
|
||
});
|
||
}
|
||
});
|
||
} catch (err) {
|
||
uni.hideLoading();
|
||
uni.showToast({ title: '加载申请列表失败', icon: 'none' });
|
||
}
|
||
};
|
||
|
||
const handleJoinParty = (post) => {
|
||
if (!ensureLoginOrRedirect('/pages/community/index')) {
|
||
return;
|
||
}
|
||
if (post.hasApplied && post.myApplyStatus !== 'REJECTED') {
|
||
uni.showToast({
|
||
title: post.myApplyStatus === 'ACCEPTED' ? '房主已同意您的上车申请' : '已提交过申请,请等待房主确认',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
uni.showModal({
|
||
title: '搭子互动申请',
|
||
content: `确定向【${post.author}】发起同城约伴 / 上车请求?`,
|
||
confirmText: '立即发起',
|
||
confirmColor: '#6366F1',
|
||
success: async (res) => {
|
||
if (res.confirm) {
|
||
uni.showLoading({ title: '正在提交申请...' });
|
||
const profile = currentUserProfile.value.nickname
|
||
? currentUserProfile.value
|
||
: await syncCurrentUserProfile();
|
||
const uid = normalizeUserId();
|
||
const applicantName = profile.nickname || '同城用户';
|
||
const applicantAvatar = profile.avatar || DEFAULT_USER_AVATAR;
|
||
try {
|
||
await applyPostParty(post.id, {
|
||
applicantId: uid,
|
||
applicantName,
|
||
applicantAvatar
|
||
});
|
||
} catch (err) {
|
||
uni.hideLoading();
|
||
uni.showToast({ title: (err && err.message) || '申请失败,请重试', icon: 'none' });
|
||
return;
|
||
}
|
||
|
||
post.hasApplied = true;
|
||
post.myApplyStatus = 'PENDING';
|
||
post.actionText = '待房主确认';
|
||
let localAppliedIds = uni.getStorageSync('COMMUNITY_APPLIED_POST_IDS') || [];
|
||
if (!Array.isArray(localAppliedIds)) localAppliedIds = [];
|
||
const strId = String(post.id);
|
||
if (!localAppliedIds.includes(strId)) {
|
||
localAppliedIds.push(strId);
|
||
uni.setStorageSync('COMMUNITY_APPLIED_POST_IDS', localAppliedIds);
|
||
}
|
||
|
||
uni.hideLoading();
|
||
uni.showToast({
|
||
title: '申请已送达,等待房主确认!',
|
||
icon: 'success'
|
||
});
|
||
}
|
||
}
|
||
});
|
||
};
|
||
|
||
const previewImage = (imgs, index) => {
|
||
uni.previewImage({
|
||
urls: imgs,
|
||
current: index
|
||
});
|
||
};
|
||
|
||
// 发动态功能模块
|
||
const showPublishModal = ref(false);
|
||
const publishCategories = [
|
||
{ key: 'partner', name: '同城搭子' },
|
||
{ key: 'game', name: '车队开黑' },
|
||
{ key: 'highlight', name: '生活随笔' }
|
||
];
|
||
|
||
const newPostForm = ref({
|
||
category: 'partner',
|
||
content: '',
|
||
selectedTopics: [],
|
||
images: [],
|
||
location: ''
|
||
});
|
||
|
||
const contentPlaceholder = computed(() => {
|
||
if (newPostForm.value.category === 'partner') {
|
||
return '分享你的同城聚会计划、想去吃的美食或找搭子需求(如:今晚三里屯涮肉AA,差2人)...';
|
||
}
|
||
if (newPostForm.value.category === 'game') {
|
||
return '发布你的开黑车队信息、段位要求或开黑时间(如:王者荣耀上分车队,缺软辅妹子/中单)...';
|
||
}
|
||
return '记录你今天的高光时刻、生活随笔或心情打卡...';
|
||
});
|
||
|
||
const isTopicSelected = (t) => {
|
||
return newPostForm.value.selectedTopics.includes(t);
|
||
};
|
||
|
||
const toggleTopic = (t) => {
|
||
const idx = newPostForm.value.selectedTopics.indexOf(t);
|
||
if (idx > -1) {
|
||
newPostForm.value.selectedTopics.splice(idx, 1);
|
||
} else {
|
||
if (newPostForm.value.selectedTopics.length >= 3) {
|
||
uni.showToast({ title: '最多添加3个话题标签', icon: 'none' });
|
||
return;
|
||
}
|
||
newPostForm.value.selectedTopics.push(t);
|
||
}
|
||
};
|
||
|
||
const selectPublishCategory = (key) => {
|
||
newPostForm.value.category = key;
|
||
};
|
||
|
||
const choosePostImage = () => {
|
||
const remainCount = 9 - newPostForm.value.images.length;
|
||
if (remainCount <= 0) return;
|
||
|
||
uni.chooseImage({
|
||
count: remainCount,
|
||
sizeType: ['compressed'],
|
||
sourceType: ['album', 'camera'],
|
||
success: (res) => {
|
||
newPostForm.value.images = newPostForm.value.images.concat(res.tempFilePaths);
|
||
}
|
||
});
|
||
};
|
||
|
||
const removeImage = (idx) => {
|
||
newPostForm.value.images.splice(idx, 1);
|
||
};
|
||
|
||
const openPublishModal = (catKey = 'partner') => {
|
||
if (!ensureLoginOrRedirect('/pages/community/index')) {
|
||
return;
|
||
}
|
||
newPostForm.value.category = catKey;
|
||
showPublishModal.value = true;
|
||
};
|
||
|
||
const closePublishModal = () => {
|
||
showPublishModal.value = false;
|
||
};
|
||
|
||
const stopPropagation = (e) => {
|
||
if (e && typeof e.stopPropagation === 'function') {
|
||
e.stopPropagation();
|
||
}
|
||
};
|
||
|
||
const handlePublishPost = () => {
|
||
if (!ensureLoginOrRedirect('/pages/community/index')) {
|
||
return;
|
||
}
|
||
uni.showActionSheet({
|
||
itemList: ['发同城美食搭子', '发开黑车队招募', '发生活高光随笔'],
|
||
success: (res) => {
|
||
const catMap = ['partner', 'game', 'highlight'];
|
||
openPublishModal(catMap[res.tapIndex] || 'partner');
|
||
},
|
||
fail: () => {
|
||
// 若用户点击灰色蒙层取消 ActionSheet,直接拉起默认搭子发布器
|
||
openPublishModal('partner');
|
||
}
|
||
});
|
||
};
|
||
|
||
const submitPublishPost = async () => {
|
||
const content = newPostForm.value.content.trim();
|
||
if (!content) {
|
||
uni.showToast({
|
||
title: '请输入动态正文内容',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
const tagMap = {
|
||
partner: { text: '同城搭子', type: 'warm-tag', action: '加入车队' },
|
||
game: { text: '国服大神', type: 'cool-tag', action: '立即上车' },
|
||
highlight: { text: '生活达人', type: 'gold-tag', action: '聊一聊' }
|
||
};
|
||
const currentMeta = tagMap[newPostForm.value.category] || tagMap.partner;
|
||
|
||
uni.showLoading({ title: '正在上传与发布...' });
|
||
const profile = currentUserProfile.value.nickname
|
||
? currentUserProfile.value
|
||
: await syncCurrentUserProfile();
|
||
const uid = normalizeUserId();
|
||
const authorName = profile.nickname || '同城用户';
|
||
const authorAvatar = profile.avatar || '';
|
||
|
||
let finalImages = [];
|
||
try {
|
||
if (newPostForm.value.images.length > 0) {
|
||
finalImages = await Promise.all(
|
||
newPostForm.value.images.map(img => uploadImage(img))
|
||
);
|
||
}
|
||
} catch (uploadErr) {
|
||
console.warn('[Community] 图片上传失败:', uploadErr);
|
||
uni.hideLoading();
|
||
uni.showToast({ title: '图片上传失败,请重试后再发布', icon: 'none' });
|
||
return;
|
||
}
|
||
|
||
const postPayload = {
|
||
userId: uid,
|
||
author: authorName,
|
||
avatar: authorAvatar,
|
||
tag: currentMeta.text,
|
||
tagType: currentMeta.type,
|
||
location: newPostForm.value.location,
|
||
content: content,
|
||
topics: [...newPostForm.value.selectedTopics],
|
||
images: finalImages,
|
||
actionText: currentMeta.action,
|
||
category: newPostForm.value.category
|
||
};
|
||
|
||
try {
|
||
const res = await createPost(postPayload);
|
||
if (!res || !res.id) {
|
||
throw new Error('发布失败:服务端未返回动态编号');
|
||
}
|
||
const createdPost = {
|
||
id: res.id,
|
||
userId: uid,
|
||
author: (res && res.author) || postPayload.author,
|
||
avatar: (res && res.avatar) || postPayload.avatar,
|
||
tag: currentMeta.text,
|
||
tagType: currentMeta.type,
|
||
location: postPayload.location,
|
||
distance: '',
|
||
time: '刚刚',
|
||
content: content,
|
||
topics: postPayload.topics,
|
||
images: finalImages,
|
||
actionText: currentMeta.action,
|
||
likes: 0,
|
||
liked: false,
|
||
comments: 0,
|
||
views: 1,
|
||
category: postPayload.category
|
||
};
|
||
|
||
// 即时将新发布的动态插入列表顶端
|
||
posts.value.unshift(createdPost);
|
||
|
||
// 若当前 Tab 过滤了该分类,自动切换为全部或当前分类,确保用户即刻看到
|
||
if (currentTab.value !== 'all' && currentTab.value !== newPostForm.value.category) {
|
||
currentTab.value = 'all';
|
||
}
|
||
|
||
// 关闭弹窗并重置
|
||
closePublishModal();
|
||
newPostForm.value.content = '';
|
||
newPostForm.value.images = [];
|
||
newPostForm.value.selectedTopics = ['三里屯探店'];
|
||
|
||
uni.hideLoading();
|
||
uni.showToast({
|
||
title: '动态发布成功!',
|
||
icon: 'success',
|
||
duration: 1800
|
||
});
|
||
|
||
// 平滑滚动回顶部
|
||
uni.pageScrollTo({
|
||
scrollTop: 0,
|
||
duration: 300
|
||
});
|
||
} catch (err) {
|
||
uni.hideLoading();
|
||
uni.showToast({
|
||
title: err.message || '发布失败,请重试',
|
||
icon: 'none',
|
||
duration: 2500
|
||
});
|
||
}
|
||
};
|
||
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
.community-container {
|
||
min-height: 100vh;
|
||
background-color: #0A0F1D;
|
||
color: #F8FAFC;
|
||
padding: 0 28rpx 24rpx;
|
||
box-sizing: border-box;
|
||
|
||
.community-header {
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding-top: var(--safe-header-gap, 50rpx);
|
||
margin-bottom: 20rpx;
|
||
|
||
.header-title-row {
|
||
display: flex;
|
||
align-items: center;
|
||
height: var(--capsule-height, 64rpx);
|
||
/* #ifdef MP-WEIXIN */
|
||
padding-right: calc(var(--capsule-right-space, 210rpx));
|
||
/* #endif */
|
||
|
||
.page-title {
|
||
font-size: 40rpx;
|
||
font-weight: 800;
|
||
color: #FFFFFF;
|
||
letter-spacing: 1rpx;
|
||
line-height: 1;
|
||
}
|
||
}
|
||
|
||
.header-sub-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-top: 10rpx;
|
||
min-height: 56rpx;
|
||
|
||
.page-sub {
|
||
font-size: 22rpx;
|
||
color: #94A3B8;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
flex: 1;
|
||
margin-right: 16rpx;
|
||
}
|
||
|
||
.publish-btn {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 10rpx;
|
||
background: linear-gradient(135deg, #6366F1 0%, #8B5CF6 100%);
|
||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||
padding: 0 28rpx;
|
||
height: 58rpx;
|
||
border-radius: 29rpx;
|
||
box-shadow: 0 6rpx 18rpx rgba(99, 102, 241, 0.36);
|
||
flex-shrink: 0;
|
||
box-sizing: border-box;
|
||
|
||
.publish-icon {
|
||
width: 28rpx;
|
||
height: 28rpx;
|
||
flex-shrink: 0;
|
||
display: block;
|
||
}
|
||
|
||
.publish-text {
|
||
font-size: 25rpx;
|
||
font-weight: 700;
|
||
color: #FFFFFF;
|
||
line-height: 1;
|
||
letter-spacing: 1rpx;
|
||
white-space: nowrap;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.community-notice-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
background: rgba(99, 102, 241, 0.12);
|
||
border: 1px solid rgba(99, 102, 241, 0.3);
|
||
border-radius: 14rpx;
|
||
padding: 12rpx 16rpx 12rpx 20rpx;
|
||
margin-bottom: 20rpx;
|
||
gap: 14rpx;
|
||
cursor: pointer;
|
||
box-sizing: border-box;
|
||
|
||
.notice-badge {
|
||
font-size: 20rpx;
|
||
font-weight: 700;
|
||
color: #FFFFFF;
|
||
background: linear-gradient(135deg, #F43F5E, #E11D48);
|
||
padding: 4rpx 14rpx;
|
||
border-radius: 6rpx;
|
||
flex-shrink: 0;
|
||
letter-spacing: 0.5rpx;
|
||
}
|
||
|
||
.notice-marquee-wrap {
|
||
flex: 1;
|
||
overflow: hidden;
|
||
white-space: nowrap;
|
||
position: relative;
|
||
height: 36rpx;
|
||
line-height: 36rpx;
|
||
|
||
.notice-marquee-content {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
white-space: nowrap;
|
||
animation: marqueeScroll 24s linear infinite;
|
||
|
||
.notice-title-text {
|
||
font-size: 24rpx;
|
||
font-weight: 600;
|
||
color: #F1F5F9;
|
||
padding-right: 48rpx;
|
||
}
|
||
|
||
.marquee-clone {
|
||
font-size: 24rpx;
|
||
font-weight: 600;
|
||
color: #F1F5F9;
|
||
padding-right: 48rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
.notice-action-hint {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 2rpx;
|
||
flex-shrink: 0;
|
||
padding-left: 6rpx;
|
||
|
||
.hint-text {
|
||
font-size: 20rpx;
|
||
color: #818CF8;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.hint-arrow {
|
||
width: 22rpx;
|
||
height: 22rpx;
|
||
opacity: 0.8;
|
||
}
|
||
}
|
||
}
|
||
|
||
@keyframes marqueeScroll {
|
||
0% {
|
||
transform: translateX(0);
|
||
}
|
||
100% {
|
||
transform: translateX(-50%);
|
||
}
|
||
}
|
||
|
||
.notice-modal-mask {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 999;
|
||
background: rgba(0, 0, 0, 0.68);
|
||
backdrop-filter: blur(8px);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 40rpx;
|
||
box-sizing: border-box;
|
||
|
||
.notice-modal-content {
|
||
width: 100%;
|
||
max-height: 82vh;
|
||
background: #151D30;
|
||
border: 1px solid rgba(99, 102, 241, 0.35);
|
||
border-radius: 28rpx;
|
||
box-shadow: 0 16rpx 48rpx rgba(0, 0, 0, 0.45);
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
|
||
.modal-header {
|
||
padding: 32rpx 32rpx 20rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||
|
||
.modal-header-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 14rpx;
|
||
flex: 1;
|
||
overflow: hidden;
|
||
|
||
.modal-badge {
|
||
font-size: 20rpx;
|
||
font-weight: 700;
|
||
color: #FFFFFF;
|
||
background: linear-gradient(135deg, #F43F5E, #E11D48);
|
||
padding: 4rpx 14rpx;
|
||
border-radius: 6rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.modal-title {
|
||
font-size: 30rpx;
|
||
font-weight: 700;
|
||
color: #FFFFFF;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
}
|
||
|
||
.modal-close {
|
||
width: 56rpx;
|
||
height: 56rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba(255, 255, 255, 0.06);
|
||
border-radius: 28rpx;
|
||
flex-shrink: 0;
|
||
|
||
.close-icon {
|
||
width: 28rpx;
|
||
height: 28rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
.notice-modal-body {
|
||
max-height: 58vh;
|
||
padding: 24rpx 32rpx;
|
||
box-sizing: border-box;
|
||
|
||
.notice-lead-card {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 16rpx;
|
||
background: rgba(99, 102, 241, 0.15);
|
||
border: 1px solid rgba(99, 102, 241, 0.3);
|
||
border-radius: 16rpx;
|
||
padding: 20rpx;
|
||
margin-bottom: 24rpx;
|
||
|
||
.lead-icon {
|
||
width: 36rpx;
|
||
height: 36rpx;
|
||
flex-shrink: 0;
|
||
margin-top: 4rpx;
|
||
}
|
||
|
||
.lead-text {
|
||
font-size: 24rpx;
|
||
line-height: 1.6;
|
||
color: #E2E8F0;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
|
||
.notice-rules-section {
|
||
.rules-header {
|
||
font-size: 26rpx;
|
||
font-weight: 700;
|
||
color: #94A3B8;
|
||
margin-bottom: 16rpx;
|
||
letter-spacing: 0.5rpx;
|
||
}
|
||
|
||
.rule-item {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 16rpx;
|
||
background: rgba(255, 255, 255, 0.04);
|
||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||
border-radius: 16rpx;
|
||
padding: 20rpx;
|
||
margin-bottom: 16rpx;
|
||
|
||
.rule-index {
|
||
width: 44rpx;
|
||
height: 44rpx;
|
||
border-radius: 12rpx;
|
||
background: rgba(99, 102, 241, 0.2);
|
||
color: #818CF8;
|
||
font-size: 22rpx;
|
||
font-weight: 800;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.rule-body {
|
||
flex: 1;
|
||
|
||
.rule-title {
|
||
display: block;
|
||
font-size: 26rpx;
|
||
font-weight: 700;
|
||
color: #FFFFFF;
|
||
margin-bottom: 6rpx;
|
||
}
|
||
|
||
.rule-desc {
|
||
display: block;
|
||
font-size: 22rpx;
|
||
line-height: 1.5;
|
||
color: #94A3B8;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.notice-modal-footer {
|
||
padding: 20rpx 32rpx 32rpx;
|
||
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||
|
||
.modal-confirm-btn {
|
||
width: 100%;
|
||
height: 84rpx;
|
||
line-height: 84rpx;
|
||
border-radius: 42rpx;
|
||
background: linear-gradient(135deg, #6366F1 0%, #8B5CF6 100%);
|
||
color: #FFFFFF;
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
box-shadow: 0 4rpx 16rpx rgba(99, 102, 241, 0.35);
|
||
margin: 0;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.feed-tabs-wrap {
|
||
margin-bottom: 24rpx;
|
||
width: 100%;
|
||
|
||
.feed-scroll {
|
||
white-space: nowrap;
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
|
||
&::-webkit-scrollbar {
|
||
display: none;
|
||
width: 0;
|
||
height: 0;
|
||
}
|
||
}
|
||
|
||
.tabs-flex {
|
||
display: inline-flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
padding: 16rpx 10rpx 10rpx;
|
||
|
||
.tab-pill {
|
||
display: inline-flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
flex-shrink: 0;
|
||
white-space: nowrap;
|
||
position: relative;
|
||
padding: 14rpx 28rpx;
|
||
background: #151D30;
|
||
border-radius: 32rpx;
|
||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||
transition: all 0.15s ease-out;
|
||
|
||
.tab-icon-svg {
|
||
width: 28rpx;
|
||
height: 28rpx;
|
||
margin-right: 10rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.pill-text {
|
||
font-size: 24rpx;
|
||
color: #94A3B8;
|
||
font-weight: 500;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.tab-badge {
|
||
position: absolute;
|
||
top: -10rpx;
|
||
right: -8rpx;
|
||
height: 26rpx;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: linear-gradient(135deg, #FF3B30 0%, #FF2D55 100%);
|
||
border: 2rpx solid #0A0F1D;
|
||
padding: 0 10rpx;
|
||
border-radius: 14rpx;
|
||
box-shadow: 0 4rpx 12rpx rgba(255, 45, 85, 0.5);
|
||
z-index: 3;
|
||
pointer-events: none;
|
||
|
||
.badge-text {
|
||
color: #FFFFFF;
|
||
font-size: 16rpx;
|
||
font-weight: 800;
|
||
font-style: italic;
|
||
line-height: 1;
|
||
letter-spacing: 1rpx;
|
||
}
|
||
}
|
||
|
||
&.active {
|
||
background: linear-gradient(135deg, #6366F1 0%, #8B5CF6 100%);
|
||
border-color: #818CF8;
|
||
box-shadow: 0 4rpx 16rpx rgba(99, 102, 241, 0.45);
|
||
|
||
.pill-text {
|
||
color: #FFFFFF;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.posts-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 24rpx;
|
||
|
||
.posts-empty {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 10rpx;
|
||
padding: 80rpx 24rpx;
|
||
border-radius: 24rpx;
|
||
border: 1rpx dashed rgba(255, 255, 255, 0.12);
|
||
background: rgba(255, 255, 255, 0.03);
|
||
|
||
.posts-empty-title {
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
color: #E2E8F0;
|
||
}
|
||
|
||
.posts-empty-desc {
|
||
font-size: 24rpx;
|
||
color: #94A3B8;
|
||
}
|
||
}
|
||
|
||
.post-card {
|
||
background: #151D30;
|
||
border-radius: 24rpx;
|
||
padding: 24rpx;
|
||
border: 1rpx solid rgba(255, 255, 255, 0.06);
|
||
|
||
.author-row {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 18rpx;
|
||
|
||
.author-avatar {
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
border-radius: 50%;
|
||
border: 2rpx solid #334155;
|
||
margin-right: 16rpx;
|
||
}
|
||
|
||
.author-meta {
|
||
flex: 1;
|
||
|
||
.name-line {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
|
||
.author-name {
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
color: #F8FAFC;
|
||
}
|
||
|
||
.author-tag {
|
||
font-size: 18rpx;
|
||
padding: 2rpx 10rpx;
|
||
border-radius: 8rpx;
|
||
font-weight: 600;
|
||
|
||
&.warm-tag {
|
||
background: rgba(245, 158, 11, 0.15);
|
||
color: #F59E0B;
|
||
border: 1rpx solid rgba(245, 158, 11, 0.3);
|
||
}
|
||
&.cool-tag {
|
||
background: rgba(99, 102, 241, 0.15);
|
||
color: #818CF8;
|
||
border: 1rpx solid rgba(99, 102, 241, 0.3);
|
||
}
|
||
&.gold-tag {
|
||
background: rgba(236, 72, 153, 0.15);
|
||
color: #F472B6;
|
||
border: 1rpx solid rgba(236, 72, 153, 0.3);
|
||
}
|
||
}
|
||
}
|
||
|
||
.sub-line {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
margin-top: 4rpx;
|
||
|
||
.post-time, .dot-sep {
|
||
font-size: 20rpx;
|
||
color: #64748B;
|
||
}
|
||
|
||
.distance-box {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4rpx;
|
||
|
||
.dist-icon {
|
||
width: 20rpx;
|
||
height: 20rpx;
|
||
}
|
||
|
||
.dist-text {
|
||
font-size: 20rpx;
|
||
color: #38BDF8;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.author-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
|
||
.btn-delete-post {
|
||
background: rgba(239, 68, 68, 0.12);
|
||
color: #EF4444;
|
||
border: 1rpx solid rgba(239, 68, 68, 0.35);
|
||
font-size: 20rpx;
|
||
padding: 6rpx 20rpx;
|
||
border-radius: 24rpx;
|
||
font-weight: 600;
|
||
margin: 0;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.join-btn {
|
||
background: rgba(99, 102, 241, 0.15);
|
||
color: #818CF8;
|
||
border: 1rpx solid rgba(99, 102, 241, 0.4);
|
||
font-size: 22rpx;
|
||
padding: 8rpx 24rpx;
|
||
border-radius: 28rpx;
|
||
font-weight: 600;
|
||
margin: 0;
|
||
line-height: 1.5;
|
||
|
||
&.btn-applied {
|
||
background: rgba(148, 163, 184, 0.15);
|
||
color: #94A3B8;
|
||
border-color: rgba(148, 163, 184, 0.3);
|
||
opacity: 0.8;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.post-content {
|
||
font-size: 26rpx;
|
||
line-height: 1.6;
|
||
color: #E2E8F0;
|
||
display: block;
|
||
margin-bottom: 14rpx;
|
||
}
|
||
|
||
.post-topics {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 12rpx;
|
||
margin-bottom: 16rpx;
|
||
|
||
.topic-item {
|
||
font-size: 22rpx;
|
||
color: #818CF8;
|
||
background: rgba(99, 102, 241, 0.1);
|
||
padding: 4rpx 12rpx;
|
||
border-radius: 8rpx;
|
||
}
|
||
}
|
||
|
||
.post-images {
|
||
display: flex;
|
||
gap: 12rpx;
|
||
margin-bottom: 18rpx;
|
||
|
||
.post-img {
|
||
width: 200rpx;
|
||
height: 200rpx;
|
||
border-radius: 16rpx;
|
||
}
|
||
}
|
||
|
||
.post-footer {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding-top: 16rpx;
|
||
border-top: 1rpx solid rgba(255, 255, 255, 0.05);
|
||
|
||
.footer-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 32rpx;
|
||
|
||
.action-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
|
||
.action-icon {
|
||
width: 28rpx;
|
||
height: 28rpx;
|
||
}
|
||
|
||
.action-count {
|
||
font-size: 22rpx;
|
||
color: #94A3B8;
|
||
|
||
&.liked {
|
||
color: #EF4444;
|
||
font-weight: 700;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.view-tag {
|
||
font-size: 20rpx;
|
||
color: #64748B;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 发动态弹窗编辑器样式 (现代高级 Bottom Sheet 社交画廊风格) */
|
||
.publish-modal-mask {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.72);
|
||
backdrop-filter: blur(10px);
|
||
z-index: 99999;
|
||
display: flex;
|
||
align-items: flex-end;
|
||
justify-content: center;
|
||
|
||
.publish-modal-box {
|
||
width: 100%;
|
||
max-width: 750rpx;
|
||
max-height: 86vh;
|
||
overflow-y: auto;
|
||
-webkit-overflow-scrolling: touch;
|
||
background: #151D30;
|
||
border-top-left-radius: 40rpx;
|
||
border-top-right-radius: 40rpx;
|
||
padding: 24rpx 32rpx calc(36rpx + env(safe-area-inset-bottom));
|
||
box-sizing: border-box;
|
||
border-top: 1rpx solid rgba(255, 255, 255, 0.12);
|
||
box-shadow: 0 -16rpx 48rpx rgba(0, 0, 0, 0.55);
|
||
animation: modalSlideUp 0.28s cubic-bezier(0.16, 1, 0.3, 1);
|
||
|
||
/* 居中下拉把手 */
|
||
.sheet-drag-handle {
|
||
width: 72rpx;
|
||
height: 8rpx;
|
||
border-radius: 4rpx;
|
||
background: rgba(255, 255, 255, 0.22);
|
||
margin: 0 auto 28rpx;
|
||
}
|
||
|
||
/* 弹窗顶栏 */
|
||
.modal-header {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
margin-bottom: 28rpx;
|
||
|
||
.header-titles {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6rpx;
|
||
|
||
.modal-title {
|
||
font-size: 34rpx;
|
||
font-weight: 800;
|
||
color: #F8FAFC;
|
||
letter-spacing: 0.5rpx;
|
||
}
|
||
|
||
.modal-sub {
|
||
font-size: 22rpx;
|
||
color: #94A3B8;
|
||
font-weight: 400;
|
||
}
|
||
}
|
||
|
||
.close-btn {
|
||
width: 56rpx;
|
||
height: 56rpx;
|
||
border-radius: 50%;
|
||
background: rgba(255, 255, 255, 0.08);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: all 0.2s;
|
||
|
||
.close-icon {
|
||
width: 24rpx;
|
||
height: 24rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 分类分段控制器 */
|
||
.segment-tabs-container {
|
||
display: flex;
|
||
background: rgba(255, 255, 255, 0.05);
|
||
padding: 6rpx;
|
||
border-radius: 20rpx;
|
||
margin-bottom: 24rpx;
|
||
border: 1rpx solid rgba(255, 255, 255, 0.05);
|
||
|
||
.segment-item {
|
||
flex: 1;
|
||
text-align: center;
|
||
padding: 14rpx 0;
|
||
border-radius: 16rpx;
|
||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||
|
||
.segment-text {
|
||
font-size: 24rpx;
|
||
font-weight: 500;
|
||
color: #94A3B8;
|
||
}
|
||
|
||
&.active {
|
||
background: linear-gradient(135deg, #6366F1, #8B5CF6);
|
||
box-shadow: 0 4rpx 14rpx rgba(99, 102, 241, 0.35);
|
||
|
||
.segment-text {
|
||
color: #FFFFFF;
|
||
font-weight: 700;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 沉浸式文本输入卡片 */
|
||
.textarea-card {
|
||
background: #0A0F1D;
|
||
border: 1rpx solid rgba(255, 255, 255, 0.08);
|
||
border-radius: 24rpx;
|
||
padding: 22rpx;
|
||
position: relative;
|
||
margin-bottom: 26rpx;
|
||
box-sizing: border-box;
|
||
|
||
.post-textarea {
|
||
width: 100%;
|
||
height: 200rpx;
|
||
font-size: 26rpx;
|
||
line-height: 1.6;
|
||
color: #F8FAFC;
|
||
}
|
||
|
||
.ph {
|
||
color: #64748B;
|
||
font-size: 25rpx;
|
||
}
|
||
|
||
.textarea-corner-meta {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
margin-top: 8rpx;
|
||
|
||
.char-count-badge {
|
||
font-size: 20rpx;
|
||
color: #64748B;
|
||
background: rgba(255, 255, 255, 0.05);
|
||
padding: 4rpx 14rpx;
|
||
border-radius: 12rpx;
|
||
font-variant-numeric: tabular-nums;
|
||
|
||
&.near-limit {
|
||
color: #F87171;
|
||
background: rgba(248, 113, 113, 0.12);
|
||
}
|
||
|
||
.current-len {
|
||
font-weight: 600;
|
||
}
|
||
|
||
.max-len {
|
||
opacity: 0.65;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 话题流式微胶囊区 */
|
||
.topics-section {
|
||
margin-bottom: 26rpx;
|
||
|
||
.section-title-row {
|
||
display: flex;
|
||
align-items: baseline;
|
||
justify-content: space-between;
|
||
margin-bottom: 14rpx;
|
||
|
||
.title-text {
|
||
font-size: 24rpx;
|
||
font-weight: 700;
|
||
color: #CBD5E1;
|
||
}
|
||
|
||
.title-tip {
|
||
font-size: 20rpx;
|
||
color: #64748B;
|
||
}
|
||
}
|
||
|
||
.topics-flex {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 14rpx;
|
||
|
||
.topic-chip {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4rpx;
|
||
padding: 10rpx 22rpx;
|
||
border-radius: 24rpx;
|
||
background: rgba(99, 102, 241, 0.08);
|
||
border: 1rpx solid rgba(99, 102, 241, 0.2);
|
||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||
|
||
.hash-tag {
|
||
font-size: 22rpx;
|
||
font-weight: 700;
|
||
color: #818CF8;
|
||
opacity: 0.8;
|
||
}
|
||
|
||
.topic-name {
|
||
font-size: 22rpx;
|
||
font-weight: 500;
|
||
color: #C7D2FE;
|
||
}
|
||
|
||
&.selected {
|
||
background: #6366F1;
|
||
border-color: #6366F1;
|
||
box-shadow: 0 4rpx 14rpx rgba(99, 102, 241, 0.35);
|
||
|
||
.hash-tag {
|
||
color: #FFFFFF;
|
||
opacity: 1;
|
||
}
|
||
|
||
.topic-name {
|
||
color: #FFFFFF;
|
||
font-weight: 700;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 画廊九宫格图片区 */
|
||
.gallery-section {
|
||
margin-bottom: 26rpx;
|
||
|
||
.gallery-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 16rpx;
|
||
|
||
.grid-img-item {
|
||
position: relative;
|
||
width: 100%;
|
||
aspect-ratio: 1;
|
||
border-radius: 18rpx;
|
||
overflow: hidden;
|
||
background: #0A0F1D;
|
||
|
||
.thumb-img {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.remove-btn {
|
||
position: absolute;
|
||
top: 10rpx;
|
||
right: 10rpx;
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
border-radius: 50%;
|
||
background: rgba(15, 23, 42, 0.85);
|
||
backdrop-filter: blur(4px);
|
||
border: 2rpx solid rgba(255, 255, 255, 0.6);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 2;
|
||
|
||
.remove-icon {
|
||
width: 18rpx;
|
||
height: 18rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
.add-img-tile {
|
||
aspect-ratio: 1;
|
||
border-radius: 18rpx;
|
||
background: rgba(255, 255, 255, 0.04);
|
||
border: 2rpx dashed rgba(255, 255, 255, 0.16);
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8rpx;
|
||
transition: all 0.2s;
|
||
|
||
.tile-icon-circle {
|
||
width: 58rpx;
|
||
height: 58rpx;
|
||
border-radius: 50%;
|
||
background: rgba(255, 255, 255, 0.06);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
.cam-icon {
|
||
width: 30rpx;
|
||
height: 30rpx;
|
||
}
|
||
}
|
||
|
||
.tile-label {
|
||
font-size: 20rpx;
|
||
font-weight: 600;
|
||
color: #CBD5E1;
|
||
}
|
||
|
||
.tile-count {
|
||
font-size: 18rpx;
|
||
color: #64748B;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 定位信息微胶囊 */
|
||
.meta-row {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 30rpx;
|
||
|
||
.location-pill {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
background: rgba(56, 189, 248, 0.08);
|
||
border: 1rpx solid rgba(56, 189, 248, 0.2);
|
||
padding: 8rpx 20rpx;
|
||
border-radius: 30rpx;
|
||
|
||
.loc-pin {
|
||
width: 22rpx;
|
||
height: 22rpx;
|
||
}
|
||
|
||
.loc-name {
|
||
font-size: 22rpx;
|
||
font-weight: 500;
|
||
color: #38BDF8;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 底部操作按钮栏 */
|
||
.modal-footer-row {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
align-items: center;
|
||
|
||
.action-btn-cancel {
|
||
flex: 1;
|
||
height: 88rpx;
|
||
line-height: 88rpx;
|
||
border-radius: 44rpx;
|
||
background: rgba(255, 255, 255, 0.08);
|
||
color: #94A3B8;
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
border: none;
|
||
text-align: center;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.action-btn-submit {
|
||
flex: 2;
|
||
height: 88rpx;
|
||
line-height: 88rpx;
|
||
border-radius: 44rpx;
|
||
background: linear-gradient(135deg, #6366F1, #8B5CF6);
|
||
color: #FFFFFF;
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
border: none;
|
||
text-align: center;
|
||
box-shadow: 0 6rpx 20rpx rgba(99, 102, 241, 0.4);
|
||
transition: all 0.2s;
|
||
|
||
&[disabled] {
|
||
opacity: 0.45;
|
||
box-shadow: none;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
@keyframes modalSlideUp {
|
||
from {
|
||
transform: translateY(100%);
|
||
}
|
||
to {
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
/* 浅色暖阳 (Warm Theme) 适配 */
|
||
&.theme-warm {
|
||
background-color: #FAF7F2;
|
||
color: #292524;
|
||
|
||
.community-header {
|
||
.page-title {
|
||
color: #292524;
|
||
}
|
||
.page-sub {
|
||
color: #78716C;
|
||
}
|
||
.publish-btn {
|
||
background: linear-gradient(135deg, #F97316 0%, #EA580C 100%);
|
||
border-color: rgba(255, 255, 255, 0.28);
|
||
box-shadow: 0 6rpx 18rpx rgba(234, 88, 12, 0.28);
|
||
}
|
||
}
|
||
|
||
.community-notice-bar {
|
||
background: #FFFFFF;
|
||
border: 1px solid #FED7AA;
|
||
box-shadow: 0 2rpx 10rpx rgba(234, 88, 12, 0.06);
|
||
|
||
.notice-badge {
|
||
background: linear-gradient(135deg, #EA580C, #F97316);
|
||
color: #FFFFFF;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.notice-marquee-wrap {
|
||
.notice-marquee-content {
|
||
.notice-title-text, .marquee-clone {
|
||
color: #292524;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
}
|
||
|
||
.notice-action-hint {
|
||
.hint-text {
|
||
color: #EA580C;
|
||
}
|
||
}
|
||
}
|
||
|
||
.notice-modal-mask {
|
||
background: rgba(28, 25, 23, 0.6);
|
||
|
||
.notice-modal-content {
|
||
background: #FFFFFF;
|
||
border: 1px solid #F0ECE4;
|
||
box-shadow: 0 16rpx 48rpx rgba(234, 88, 12, 0.15);
|
||
|
||
.modal-header {
|
||
border-bottom-color: #F5F5F4;
|
||
|
||
.modal-header-left {
|
||
.modal-badge {
|
||
background: linear-gradient(135deg, #EA580C, #F97316);
|
||
}
|
||
.modal-title {
|
||
color: #1C1917;
|
||
}
|
||
}
|
||
|
||
.modal-close {
|
||
background: #F5F5F4;
|
||
}
|
||
}
|
||
|
||
.notice-modal-body {
|
||
.notice-lead-card {
|
||
background: #FFF7ED;
|
||
border-color: #FED7AA;
|
||
|
||
.lead-text {
|
||
color: #C2410C;
|
||
}
|
||
}
|
||
|
||
.notice-rules-section {
|
||
.rules-header {
|
||
color: #78716C;
|
||
}
|
||
|
||
.rule-item {
|
||
background: #FDFBF7;
|
||
border-color: #F0ECE4;
|
||
|
||
.rule-index {
|
||
background: #FFF7ED;
|
||
color: #EA580C;
|
||
}
|
||
|
||
.rule-body {
|
||
.rule-title {
|
||
color: #1C1917;
|
||
}
|
||
.rule-desc {
|
||
color: #78716C;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.notice-modal-footer {
|
||
border-top-color: #F5F5F4;
|
||
|
||
.modal-confirm-btn {
|
||
background: linear-gradient(135deg, #EA580C 0%, #C2410C 100%);
|
||
box-shadow: 0 4rpx 16rpx rgba(234, 88, 12, 0.35);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.feed-tabs-wrap {
|
||
.tabs-flex {
|
||
.tab-pill {
|
||
background: #FFFFFF;
|
||
border: 1px solid #F0ECE4;
|
||
.pill-text {
|
||
color: #57534E;
|
||
}
|
||
.tab-badge {
|
||
border: 2rpx solid #FFFFFF;
|
||
}
|
||
&.active {
|
||
background: linear-gradient(135deg, rgba(234, 88, 12, 0.12), rgba(249, 115, 22, 0.05));
|
||
border-color: #EA580C;
|
||
box-shadow: none;
|
||
.pill-text {
|
||
color: #EA580C;
|
||
font-weight: 700;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.posts-list {
|
||
.post-card {
|
||
background: #FFFFFF;
|
||
border: 1rpx solid #E7E5E4;
|
||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.03);
|
||
|
||
.author-row {
|
||
.author-meta {
|
||
.name-line .author-name {
|
||
color: #292524;
|
||
}
|
||
}
|
||
.join-btn {
|
||
background: rgba(234, 88, 12, 0.1);
|
||
color: #EA580C;
|
||
border-color: rgba(234, 88, 12, 0.3);
|
||
|
||
&.btn-applied {
|
||
background: #F1F5F9;
|
||
color: #94A3B8;
|
||
border-color: #CBD5E1;
|
||
}
|
||
}
|
||
}
|
||
|
||
.post-content {
|
||
color: #44403C;
|
||
}
|
||
|
||
.post-topics .topic-item {
|
||
color: #EA580C;
|
||
background: rgba(234, 88, 12, 0.08);
|
||
}
|
||
|
||
.post-footer {
|
||
border-top-color: #F5F5F4;
|
||
}
|
||
}
|
||
}
|
||
|
||
.publish-modal-mask {
|
||
background: rgba(0, 0, 0, 0.45);
|
||
|
||
.publish-modal-box {
|
||
background: #FFFFFF;
|
||
border-top-color: #F0ECE4;
|
||
box-shadow: 0 -16rpx 48rpx rgba(28, 25, 23, 0.08);
|
||
|
||
.sheet-drag-handle {
|
||
background: #E7E5E4;
|
||
}
|
||
|
||
.modal-header {
|
||
.modal-title {
|
||
color: #1C1917;
|
||
}
|
||
.modal-sub {
|
||
color: #A8A29E;
|
||
}
|
||
.close-btn {
|
||
background: #F5F5F4;
|
||
}
|
||
}
|
||
|
||
.segment-tabs-container {
|
||
background: #F5F5F4;
|
||
border-color: #E7E5E4;
|
||
|
||
.segment-item {
|
||
.segment-text {
|
||
color: #78716C;
|
||
}
|
||
|
||
&.active {
|
||
background: linear-gradient(135deg, #EA580C, #F59E0B);
|
||
box-shadow: 0 4rpx 14rpx rgba(234, 88, 12, 0.25);
|
||
|
||
.segment-text {
|
||
color: #FFFFFF;
|
||
font-weight: 700;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.textarea-card {
|
||
background: #FAF7F2;
|
||
border-color: #EAE6DF;
|
||
|
||
.post-textarea {
|
||
color: #1C1917;
|
||
}
|
||
.ph {
|
||
color: #A8A29E;
|
||
}
|
||
|
||
.textarea-corner-meta .char-count-badge {
|
||
background: #F0ECE4;
|
||
color: #78716C;
|
||
|
||
&.near-limit {
|
||
color: #DC2626;
|
||
background: rgba(220, 38, 38, 0.1);
|
||
}
|
||
}
|
||
}
|
||
|
||
.topics-section {
|
||
.section-title-row {
|
||
.title-text {
|
||
color: #1C1917;
|
||
}
|
||
.title-tip {
|
||
color: #A8A29E;
|
||
}
|
||
}
|
||
|
||
.topics-flex .topic-chip {
|
||
background: #FAF7F2;
|
||
border-color: #E7E5E4;
|
||
|
||
.hash-tag {
|
||
color: #EA580C;
|
||
}
|
||
.topic-name {
|
||
color: #57534E;
|
||
}
|
||
|
||
&.selected {
|
||
background: #EA580C;
|
||
border-color: #EA580C;
|
||
box-shadow: 0 4rpx 14rpx rgba(234, 88, 12, 0.25);
|
||
|
||
.hash-tag,
|
||
.topic-name {
|
||
color: #FFFFFF;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.gallery-section {
|
||
.gallery-grid {
|
||
.grid-img-item {
|
||
background: #FAF7F2;
|
||
|
||
.remove-btn {
|
||
background: rgba(41, 37, 36, 0.85);
|
||
border-color: #FFFFFF;
|
||
}
|
||
}
|
||
|
||
.add-img-tile {
|
||
background: #FAF7F2;
|
||
border-color: #D6D3D1;
|
||
|
||
.tile-icon-circle {
|
||
background: #F0ECE4;
|
||
}
|
||
.tile-label {
|
||
color: #57534E;
|
||
}
|
||
.tile-count {
|
||
color: #A8A29E;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.meta-row .location-pill {
|
||
background: #FAF7F2;
|
||
border-color: #E7E5E4;
|
||
|
||
.loc-name {
|
||
color: #EA580C;
|
||
}
|
||
}
|
||
|
||
.modal-footer-row {
|
||
.action-btn-cancel {
|
||
background: #F5F5F4;
|
||
color: #78716C;
|
||
}
|
||
.action-btn-submit {
|
||
background: linear-gradient(135deg, #EA580C, #F59E0B);
|
||
box-shadow: 0 6rpx 20rpx rgba(234, 88, 12, 0.35);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.comment-drawer-mask {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.7);
|
||
backdrop-filter: blur(8px);
|
||
z-index: 999;
|
||
display: flex;
|
||
align-items: flex-end;
|
||
justify-content: center;
|
||
}
|
||
|
||
.comment-drawer-content {
|
||
width: 100%;
|
||
max-width: 600px;
|
||
height: 65vh;
|
||
background: #151D30;
|
||
border-top-left-radius: 36rpx;
|
||
border-top-right-radius: 36rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
border-bottom: none;
|
||
box-shadow: 0 -8rpx 32rpx rgba(0, 0, 0, 0.5);
|
||
animation: slideUp 0.25s ease-out;
|
||
|
||
.drawer-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 32rpx 36rpx 20rpx;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||
|
||
.drawer-header-left {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 12rpx;
|
||
|
||
.drawer-title {
|
||
font-size: 32rpx;
|
||
font-weight: 700;
|
||
color: #F8FAFC;
|
||
}
|
||
.drawer-badge {
|
||
font-size: 26rpx;
|
||
color: #94A3B8;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
|
||
.drawer-close {
|
||
width: 48rpx;
|
||
height: 48rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
.close-icon {
|
||
width: 36rpx;
|
||
height: 36rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
.comments-scroll {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: 24rpx 36rpx;
|
||
|
||
.comments-loading,
|
||
.comments-empty {
|
||
padding: 80rpx 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
|
||
.empty-icon {
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
opacity: 0.5;
|
||
}
|
||
|
||
.loading-text,
|
||
.empty-text {
|
||
font-size: 26rpx;
|
||
color: #94A3B8;
|
||
}
|
||
}
|
||
|
||
.comments-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 28rpx;
|
||
|
||
.comment-item {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
|
||
.comment-avatar {
|
||
width: 68rpx;
|
||
height: 68rpx;
|
||
border-radius: 50%;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.comment-body {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8rpx;
|
||
|
||
.comment-top {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
|
||
.comment-author {
|
||
font-size: 26rpx;
|
||
font-weight: 600;
|
||
color: #E2E8F0;
|
||
}
|
||
.comment-time {
|
||
font-size: 22rpx;
|
||
color: #64748B;
|
||
}
|
||
}
|
||
|
||
.comment-content {
|
||
font-size: 26rpx;
|
||
color: #CBD5E1;
|
||
line-height: 1.5;
|
||
word-break: break-all;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.comment-input-bar {
|
||
padding: 20rpx 36rpx calc(24rpx + env(safe-area-inset-bottom));
|
||
background: #0E1628;
|
||
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 20rpx;
|
||
|
||
.comment-input {
|
||
flex: 1;
|
||
height: 72rpx;
|
||
background: rgba(255, 255, 255, 0.06);
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
border-radius: 36rpx;
|
||
padding: 0 28rpx;
|
||
font-size: 26rpx;
|
||
color: #F8FAFC;
|
||
}
|
||
|
||
.comment-send-btn {
|
||
width: 120rpx;
|
||
height: 72rpx;
|
||
line-height: 72rpx;
|
||
font-size: 26rpx;
|
||
font-weight: 600;
|
||
border-radius: 36rpx;
|
||
background: linear-gradient(135deg, #6366F1 0%, #4F46E5 100%);
|
||
color: #FFFFFF;
|
||
text-align: center;
|
||
padding: 0;
|
||
box-shadow: 0 4rpx 12rpx rgba(99, 102, 241, 0.35);
|
||
|
||
&:disabled {
|
||
opacity: 0.5;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.theme-warm {
|
||
.comment-drawer-content {
|
||
background: #FFFFFF;
|
||
border-color: #E7E5E4;
|
||
box-shadow: 0 -8rpx 32rpx rgba(0, 0, 0, 0.1);
|
||
|
||
.drawer-header {
|
||
border-bottom-color: #F5F2EC;
|
||
|
||
.drawer-header-left {
|
||
.drawer-title {
|
||
color: #1C1917;
|
||
}
|
||
.drawer-badge {
|
||
color: #78716C;
|
||
}
|
||
}
|
||
}
|
||
|
||
.comments-scroll {
|
||
.comments-list {
|
||
.comment-item {
|
||
.comment-body {
|
||
.comment-top {
|
||
.comment-author {
|
||
color: #1C1917;
|
||
}
|
||
.comment-time {
|
||
color: #A8A29E;
|
||
}
|
||
}
|
||
.comment-content {
|
||
color: #44403C;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.comment-input-bar {
|
||
background: #FDFBF7;
|
||
border-top-color: #E7E5E4;
|
||
|
||
.comment-input {
|
||
background: #F5F2EC;
|
||
border-color: #E7E5E4;
|
||
color: #1C1917;
|
||
}
|
||
|
||
.comment-send-btn {
|
||
background: linear-gradient(135deg, #F97316 0%, #EA580C 100%);
|
||
box-shadow: 0 4rpx 12rpx rgba(234, 88, 12, 0.35);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.post-detail-modal-mask {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.72);
|
||
backdrop-filter: blur(10px);
|
||
-webkit-backdrop-filter: blur(10px);
|
||
z-index: 1010;
|
||
display: flex;
|
||
align-items: flex-end;
|
||
justify-content: center;
|
||
}
|
||
|
||
.post-detail-modal-box {
|
||
width: 100%;
|
||
max-width: 600px;
|
||
height: 82vh;
|
||
background: #151D30;
|
||
border-top-left-radius: 40rpx;
|
||
border-top-right-radius: 40rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
border-bottom: none;
|
||
box-shadow: 0 -12rpx 40rpx rgba(0, 0, 0, 0.55);
|
||
animation: slideUp 0.25s ease-out;
|
||
|
||
.sheet-drag-handle {
|
||
width: 72rpx;
|
||
height: 8rpx;
|
||
background: rgba(255, 255, 255, 0.18);
|
||
border-radius: 4rpx;
|
||
margin: 16rpx auto 8rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.detail-modal-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 16rpx 36rpx 24rpx;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||
flex-shrink: 0;
|
||
|
||
.detail-header-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
|
||
.detail-modal-title {
|
||
font-size: 32rpx;
|
||
font-weight: 700;
|
||
color: #F8FAFC;
|
||
}
|
||
|
||
.detail-category-badge {
|
||
font-size: 22rpx;
|
||
font-weight: 600;
|
||
color: #818CF8;
|
||
background: rgba(99, 102, 241, 0.16);
|
||
padding: 4rpx 16rpx;
|
||
border-radius: 20rpx;
|
||
}
|
||
}
|
||
|
||
.detail-close-btn {
|
||
width: 56rpx;
|
||
height: 56rpx;
|
||
border-radius: 28rpx;
|
||
background: rgba(255, 255, 255, 0.06);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
.close-icon {
|
||
width: 32rpx;
|
||
height: 32rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
.detail-modal-scroll {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: 28rpx 36rpx;
|
||
box-sizing: border-box;
|
||
|
||
.detail-author-card {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 28rpx;
|
||
|
||
.author-avatar {
|
||
width: 88rpx;
|
||
height: 88rpx;
|
||
border-radius: 50%;
|
||
margin-right: 20rpx;
|
||
border: 2rpx solid rgba(255, 255, 255, 0.15);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.author-meta {
|
||
flex: 1;
|
||
min-width: 0;
|
||
|
||
.name-line {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
margin-bottom: 8rpx;
|
||
|
||
.author-name {
|
||
font-size: 30rpx;
|
||
font-weight: 700;
|
||
color: #F8FAFC;
|
||
}
|
||
|
||
.author-tag {
|
||
font-size: 20rpx;
|
||
padding: 2rpx 12rpx;
|
||
border-radius: 8rpx;
|
||
font-weight: 600;
|
||
|
||
&.warm-tag {
|
||
color: #F59E0B;
|
||
background: rgba(245, 158, 11, 0.15);
|
||
}
|
||
&.cool-tag {
|
||
color: #818CF8;
|
||
background: rgba(99, 102, 241, 0.15);
|
||
}
|
||
&.gold-tag {
|
||
color: #FBBF24;
|
||
background: rgba(251, 191, 36, 0.15);
|
||
}
|
||
}
|
||
}
|
||
|
||
.sub-line {
|
||
display: flex;
|
||
align-items: center;
|
||
font-size: 22rpx;
|
||
color: #94A3B8;
|
||
|
||
.dot-sep {
|
||
margin: 0 8rpx;
|
||
}
|
||
|
||
.distance-box {
|
||
display: flex;
|
||
align-items: center;
|
||
color: #38BDF8;
|
||
|
||
.dist-icon {
|
||
width: 22rpx;
|
||
height: 22rpx;
|
||
margin-right: 4rpx;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.author-actions {
|
||
margin-left: 16rpx;
|
||
flex-shrink: 0;
|
||
|
||
.join-btn {
|
||
height: 60rpx;
|
||
line-height: 58rpx;
|
||
padding: 0 28rpx;
|
||
border-radius: 30rpx;
|
||
font-size: 24rpx;
|
||
font-weight: 600;
|
||
color: #818CF8;
|
||
background: rgba(99, 102, 241, 0.15);
|
||
border: 1px solid rgba(99, 102, 241, 0.35);
|
||
margin: 0;
|
||
|
||
&.btn-applied {
|
||
color: #94A3B8;
|
||
background: rgba(255, 255, 255, 0.06);
|
||
border-color: rgba(255, 255, 255, 0.12);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.detail-content-box {
|
||
margin-bottom: 24rpx;
|
||
|
||
.detail-content-text {
|
||
font-size: 30rpx;
|
||
line-height: 1.7;
|
||
color: #E2E8F0;
|
||
word-break: break-all;
|
||
}
|
||
}
|
||
|
||
.detail-topics-row {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 14rpx;
|
||
margin-bottom: 28rpx;
|
||
|
||
.detail-topic-pill {
|
||
font-size: 24rpx;
|
||
font-weight: 500;
|
||
color: #818CF8;
|
||
background: rgba(99, 102, 241, 0.12);
|
||
padding: 6rpx 18rpx;
|
||
border-radius: 12rpx;
|
||
}
|
||
}
|
||
|
||
.detail-images-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 16rpx;
|
||
margin-bottom: 28rpx;
|
||
|
||
.detail-post-img {
|
||
width: 100%;
|
||
height: 280rpx;
|
||
border-radius: 20rpx;
|
||
background: rgba(255, 255, 255, 0.04);
|
||
|
||
&.single-img {
|
||
grid-column: span 2;
|
||
height: 380rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
.detail-stats-strip {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 20rpx 0;
|
||
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||
margin-bottom: 28rpx;
|
||
|
||
.stats-left {
|
||
display: flex;
|
||
align-items: center;
|
||
font-size: 24rpx;
|
||
color: #94A3B8;
|
||
|
||
.stats-dot {
|
||
margin: 0 12rpx;
|
||
}
|
||
}
|
||
|
||
.detail-like-pill {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10rpx;
|
||
padding: 10rpx 24rpx;
|
||
border-radius: 28rpx;
|
||
background: rgba(255, 255, 255, 0.06);
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
|
||
.like-icon {
|
||
width: 30rpx;
|
||
height: 30rpx;
|
||
}
|
||
|
||
.like-txt {
|
||
font-size: 24rpx;
|
||
color: #CBD5E1;
|
||
font-weight: 600;
|
||
}
|
||
|
||
&.liked {
|
||
background: rgba(239, 68, 68, 0.15);
|
||
border-color: rgba(239, 68, 68, 0.35);
|
||
|
||
.like-txt {
|
||
color: #EF4444;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.detail-comments-section {
|
||
padding-bottom: 40rpx;
|
||
|
||
.comments-section-header {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 10rpx;
|
||
margin-bottom: 24rpx;
|
||
|
||
.section-title {
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
color: #F8FAFC;
|
||
}
|
||
|
||
.section-count {
|
||
font-size: 24rpx;
|
||
color: #94A3B8;
|
||
}
|
||
}
|
||
|
||
.comments-loading,
|
||
.comments-empty {
|
||
padding: 48rpx 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
|
||
.empty-icon {
|
||
width: 64rpx;
|
||
height: 64rpx;
|
||
opacity: 0.45;
|
||
}
|
||
|
||
.loading-text,
|
||
.empty-text {
|
||
font-size: 24rpx;
|
||
color: #94A3B8;
|
||
}
|
||
}
|
||
|
||
.comments-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 24rpx;
|
||
|
||
.comment-item {
|
||
display: flex;
|
||
gap: 18rpx;
|
||
|
||
.comment-avatar {
|
||
width: 64rpx;
|
||
height: 64rpx;
|
||
border-radius: 50%;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.comment-body {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6rpx;
|
||
|
||
.comment-top {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
|
||
.comment-author {
|
||
font-size: 26rpx;
|
||
font-weight: 600;
|
||
color: #E2E8F0;
|
||
}
|
||
.comment-time {
|
||
font-size: 22rpx;
|
||
color: #64748B;
|
||
}
|
||
}
|
||
|
||
.comment-content {
|
||
font-size: 26rpx;
|
||
color: #CBD5E1;
|
||
line-height: 1.5;
|
||
word-break: break-all;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.detail-bottom-bar {
|
||
padding: 20rpx 36rpx calc(24rpx + env(safe-area-inset-bottom));
|
||
background: #0E1628;
|
||
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 20rpx;
|
||
flex-shrink: 0;
|
||
|
||
.detail-comment-input {
|
||
flex: 1;
|
||
height: 72rpx;
|
||
background: rgba(255, 255, 255, 0.06);
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
border-radius: 36rpx;
|
||
padding: 0 28rpx;
|
||
font-size: 26rpx;
|
||
color: #F8FAFC;
|
||
}
|
||
|
||
.detail-send-btn {
|
||
width: 120rpx;
|
||
height: 72rpx;
|
||
line-height: 72rpx;
|
||
font-size: 26rpx;
|
||
font-weight: 600;
|
||
border-radius: 36rpx;
|
||
background: linear-gradient(135deg, #6366F1 0%, #4F46E5 100%);
|
||
color: #FFFFFF;
|
||
text-align: center;
|
||
padding: 0;
|
||
margin: 0;
|
||
box-shadow: 0 4rpx 12rpx rgba(99, 102, 241, 0.35);
|
||
|
||
&:disabled {
|
||
opacity: 0.5;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.theme-warm {
|
||
.post-detail-modal-box {
|
||
background: #FFFFFF;
|
||
border-color: #E7E5E4;
|
||
box-shadow: 0 -12rpx 40rpx rgba(28, 25, 23, 0.12);
|
||
|
||
.sheet-drag-handle {
|
||
background: #E7E5E4;
|
||
}
|
||
|
||
.detail-modal-header {
|
||
border-bottom-color: #F5F2EC;
|
||
|
||
.detail-header-left {
|
||
.detail-modal-title {
|
||
color: #1C1917;
|
||
}
|
||
.detail-category-badge {
|
||
color: #EA580C;
|
||
background: rgba(234, 88, 12, 0.1);
|
||
}
|
||
}
|
||
|
||
.detail-close-btn {
|
||
background: #F5F5F4;
|
||
}
|
||
}
|
||
|
||
.detail-modal-scroll {
|
||
.detail-author-card {
|
||
.author-meta .name-line .author-name {
|
||
color: #1C1917;
|
||
}
|
||
.author-actions .join-btn {
|
||
background: rgba(234, 88, 12, 0.1);
|
||
color: #EA580C;
|
||
border-color: rgba(234, 88, 12, 0.3);
|
||
|
||
&.btn-applied {
|
||
background: #F1F5F9;
|
||
color: #94A3B8;
|
||
border-color: #CBD5E1;
|
||
}
|
||
}
|
||
}
|
||
|
||
.detail-content-box .detail-content-text {
|
||
color: #292524;
|
||
}
|
||
|
||
.detail-topics-row .detail-topic-pill {
|
||
color: #EA580C;
|
||
background: rgba(234, 88, 12, 0.08);
|
||
}
|
||
|
||
.detail-stats-strip {
|
||
border-top-color: #F5F2EC;
|
||
border-bottom-color: #F5F2EC;
|
||
|
||
.detail-like-pill {
|
||
background: #F5F2EC;
|
||
border-color: #E7E5E4;
|
||
|
||
.like-txt {
|
||
color: #57534E;
|
||
}
|
||
|
||
&.liked {
|
||
background: rgba(239, 68, 68, 0.1);
|
||
border-color: rgba(239, 68, 68, 0.3);
|
||
|
||
.like-txt {
|
||
color: #EF4444;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.detail-comments-section {
|
||
.comments-section-header .section-title {
|
||
color: #1C1917;
|
||
}
|
||
|
||
.comments-list .comment-item .comment-body {
|
||
.comment-top .comment-author {
|
||
color: #1C1917;
|
||
}
|
||
.comment-content {
|
||
color: #44403C;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.detail-bottom-bar {
|
||
background: #FDFBF7;
|
||
border-top-color: #E7E5E4;
|
||
|
||
.detail-comment-input {
|
||
background: #F5F2EC;
|
||
border-color: #E7E5E4;
|
||
color: #1C1917;
|
||
}
|
||
|
||
.detail-send-btn {
|
||
background: linear-gradient(135deg, #F97316 0%, #EA580C 100%);
|
||
box-shadow: 0 4rpx 12rpx rgba(234, 88, 12, 0.35);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
/* ===== Premium visual layer: 友圈广场 ===== */
|
||
.community-container {
|
||
background:
|
||
radial-gradient(110% 40% at 50% -6%, rgba(99, 102, 241, 0.14), transparent 62%),
|
||
var(--surface-0);
|
||
|
||
.community-header {
|
||
background: rgba(7, 11, 20, 0.86);
|
||
backdrop-filter: blur(18px);
|
||
border-bottom: 1rpx solid rgba(255, 255, 255, 0.04);
|
||
}
|
||
|
||
.page-title {
|
||
font-size: 42rpx;
|
||
font-weight: 800;
|
||
letter-spacing: 0.5rpx;
|
||
}
|
||
|
||
.page-sub {
|
||
color: #7C89A6;
|
||
letter-spacing: 0.3rpx;
|
||
}
|
||
|
||
.publish-btn {
|
||
background: linear-gradient(135deg, #818CF8 0%, #6366F1 60%, #7C3AED 100%);
|
||
border-radius: 999rpx;
|
||
box-shadow: 0 10rpx 24rpx rgba(99, 102, 241, 0.28);
|
||
}
|
||
|
||
.post-card {
|
||
background: linear-gradient(180deg, rgba(22, 31, 51, 0.96) 0%, rgba(13, 20, 36, 0.98) 100%);
|
||
border: 1rpx solid var(--hairline);
|
||
border-radius: 26rpx;
|
||
box-shadow: 0 12rpx 30rpx rgba(0, 0, 0, 0.2);
|
||
animation: communityCardEnter 0.5s var(--ease-out) both;
|
||
|
||
.author-avatar {
|
||
border: 2rpx solid rgba(255, 255, 255, 0.12);
|
||
}
|
||
|
||
.author-name {
|
||
font-size: 27rpx;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.post-content {
|
||
font-size: 27rpx;
|
||
line-height: 1.65;
|
||
color: #D5DDEC;
|
||
}
|
||
|
||
.topic-item {
|
||
color: #A5B4FC;
|
||
background: rgba(99, 102, 241, 0.1);
|
||
border: 1rpx solid rgba(99, 102, 241, 0.18);
|
||
border-radius: 999rpx;
|
||
padding: 4rpx 12rpx;
|
||
}
|
||
|
||
.post-footer {
|
||
border-top-color: rgba(255, 255, 255, 0.045);
|
||
}
|
||
}
|
||
|
||
.posts-empty {
|
||
background: rgba(255, 255, 255, 0.02);
|
||
border: 1rpx dashed rgba(255, 255, 255, 0.09);
|
||
border-radius: 24rpx;
|
||
}
|
||
}
|
||
|
||
.community-container.theme-warm {
|
||
background:
|
||
radial-gradient(100% 38% at 50% -6%, rgba(234, 88, 12, 0.06), transparent 62%),
|
||
#FDFBF7;
|
||
|
||
.community-header {
|
||
background: rgba(253, 251, 247, 0.92);
|
||
border-bottom-color: #F0ECE4;
|
||
}
|
||
|
||
.page-title {
|
||
color: #1C1917;
|
||
}
|
||
|
||
.page-sub {
|
||
color: #A8A29E;
|
||
}
|
||
|
||
.post-card {
|
||
background: #FFFFFF;
|
||
border-color: #F0ECE4;
|
||
box-shadow: 0 12rpx 28rpx rgba(28, 25, 23, 0.05);
|
||
|
||
.post-content {
|
||
color: #44403C;
|
||
}
|
||
|
||
.topic-item {
|
||
color: #C2410C;
|
||
background: #FFF7ED;
|
||
border-color: #FED7AA;
|
||
}
|
||
|
||
.post-footer {
|
||
border-top-color: #F5F2EC;
|
||
}
|
||
}
|
||
}
|
||
|
||
@keyframes communityCardEnter {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(14rpx) scale(0.99);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0) scale(1);
|
||
}
|
||
}
|
||
</style>
|