feat(community): combine marquee scroll with detail modal for pinned notice

这个提交包含在:
xcbyy
2026-10-13 16:35:35 +08:00
父节点 859b224d65
当前提交 d08acc052a
共有 2 个文件被更改,包括 425 次插入 和 17 次删除

查看文件

@@ -21,10 +21,19 @@
</view>
</view>
<!-- 友圈置顶头条公告 (根据后台配置动态展示) -->
<view class="community-notice-bar" v-if="communityNotice && communityNotice.title">
<view class="notice-badge">{{ communityNotice.tag || '置顶公告' }}</view>
<text class="notice-title-text">{{ communityNotice.title }}:{{ communityNotice.content }}</text>
<!-- 友圈置顶头条公告 (跑马灯平滑微动与点击查看详情弹窗组合) -->
<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角标) -->
@@ -307,6 +316,66 @@
</view>
</view>
<!-- 社区置顶公告与公约详情弹窗 -->
<view class="notice-modal-mask" v-if="noticeModalVisible" @click.self="closeNoticeDetailModal">
<view class="notice-modal-content">
<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>
@@ -325,7 +394,21 @@ import { getPosts, createPost, deletePost, likePost, getPostComments, addPostCom
const isLoading = ref(true);
const currentThemeKey = ref(getCurrentThemeKey());
const navInfo = ref(getNavBarInfo());
const communityNotice = ref(null);
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',
@@ -1016,10 +1099,12 @@ const submitPublishPost = async () => {
align-items: center;
background: rgba(99, 102, 241, 0.12);
border: 1px solid rgba(99, 102, 241, 0.3);
border-radius: 12rpx;
padding: 12rpx 20rpx;
border-radius: 14rpx;
padding: 12rpx 16rpx 12rpx 20rpx;
margin-bottom: 20rpx;
gap: 14rpx;
cursor: pointer;
box-sizing: border-box;
.notice-badge {
font-size: 20rpx;
@@ -1032,14 +1117,242 @@ const submitPublishPost = async () => {
letter-spacing: 0.5rpx;
}
.notice-title-text {
font-size: 24rpx;
font-weight: 600;
color: #F1F5F9;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
.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;
}
}
}
}
@@ -1769,9 +2082,91 @@ const submitPublishPost = async () => {
font-weight: 700;
}
.notice-title-text {
color: #292524;
font-weight: 600;
.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);
}
}
}
}

查看文件

@@ -1061,6 +1061,19 @@ describe('ContentSecurityService & Compliance Architecture (TDD 微信内容安
expect(content).toContain('.expert-detail-container.theme-warm');
});
it('友圈置顶头条公告必须支持跑马灯平滑走字微动与点击呼出社区公约详情弹窗', () => {
const communityPath = path.join(rootDir, 'pwuniapp', 'pages', 'community', 'index.vue');
const content = fs.readFileSync(communityPath, 'utf-8');
expect(content).toContain('notice-marquee-wrap');
expect(content).toContain('marqueeScroll');
expect(content).toContain('openNoticeDetailModal');
expect(content).toContain('noticeModalVisible');
expect(content).toContain('notice-modal-mask');
expect(content).toContain('notice-modal-content');
expect(content).toContain('closeNoticeDetailModal');
expect(content).toContain('ICONS.chevronRight');
});
it('所有状态持久化修复代码与测试严禁包含任何系统 Emoji 违规字符', () => {
const actDetailPath = path.join(rootDir, 'pwuniapp', 'pages', 'activity', 'detail.vue');
const ticketPath = path.join(rootDir, 'pwuniapp', 'pages', 'activity', 'my-ticket.vue');