fix(modal): restrict confirmText length to 4 chars and safeguard banner null access
这个提交包含在:
@@ -15,14 +15,14 @@
|
||||
<view v-else class="index-body-wrapper">
|
||||
<!-- 精彩活动横幅轮播 (流光投影) -->
|
||||
<swiper class="banner-swiper" circular autoplay interval="4000" indicator-dots indicator-color="rgba(255,255,255,0.3)" indicator-active-color="#6366F1">
|
||||
<swiper-item v-for="banner in banners" :key="banner.id" class="banner-item" @click="goToActivity(banner)">
|
||||
<view class="banner-card" :style="{ background: banner.bg || 'linear-gradient(135deg, #F43F5E 0%, #FB7185 100%)' }">
|
||||
<image v-if="banner.image" class="banner-bg-img" :src="resolveBannerImage(banner.image)" mode="aspectFill" />
|
||||
<view v-if="banner.image" class="banner-overlay"></view>
|
||||
<swiper-item v-for="banner in banners" :key="banner?.id || banner?.title" class="banner-item" @click="goToActivity(banner)">
|
||||
<view class="banner-card" :style="{ background: banner?.bg || 'linear-gradient(135deg, #F43F5E 0%, #FB7185 100%)' }">
|
||||
<image v-if="banner?.image" class="banner-bg-img" :src="resolveBannerImage(banner?.image)" mode="aspectFill" />
|
||||
<view v-if="banner?.image" class="banner-overlay"></view>
|
||||
<view class="banner-content">
|
||||
<view class="banner-tag">{{ banner.tag || banner.badge || '热门陪伴' }}</view>
|
||||
<view class="banner-title">{{ banner.title }}</view>
|
||||
<view class="banner-sub">{{ banner.subtitle || '点击立即参与' }}</view>
|
||||
<view class="banner-tag">{{ banner?.tag || banner?.badge || '热门陪伴' }}</view>
|
||||
<view class="banner-title">{{ banner?.title }}</view>
|
||||
<view class="banner-sub">{{ banner?.subtitle || '点击立即参与' }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
@@ -185,7 +185,7 @@ const fetchLiveBanners = async () => {
|
||||
try {
|
||||
const list = await getBanners();
|
||||
if (Array.isArray(list) && list.length > 0) {
|
||||
banners.value = list;
|
||||
banners.value = list.filter(b => b && typeof b === 'object');
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('[Index] 获取Banner配置失败:', err);
|
||||
|
||||
@@ -756,12 +756,28 @@ const showInvitePoster = () => {
|
||||
const firstRate = finRules.firstReferralPercent != null ? finRules.firstReferralPercent : 5.0;
|
||||
const secondRate = finRules.secondReferralPercent != null ? finRules.secondReferralPercent : 2.0;
|
||||
const totalRate = (Number(firstRate) + Number(secondRate)).toFixed(1);
|
||||
const inviteCode = `PW${user.value.id || 1001}`;
|
||||
|
||||
uni.showModal({
|
||||
title: '二级分销推广',
|
||||
content: `您的专属邀请码为:PW${user.value.id}\n已成功邀请 ${user.value.invitedCount} 位好友!\n好友下单享受直推 ${firstRate}% + 间推 ${secondRate}%,全网自动分润最高达 ${totalRate}%!`,
|
||||
showCancel: false,
|
||||
confirmText: '保存推广码'
|
||||
content: `您的专属邀请码为:${inviteCode}\n已成功邀请 ${user.value.invitedCount || 0} 位好友!\n好友下单享受直推 ${firstRate}% + 间推 ${secondRate}%,全网自动分润最高达 ${totalRate}%!`,
|
||||
showCancel: true,
|
||||
cancelText: '我知道了',
|
||||
confirmText: '一键复制',
|
||||
confirmColor: '#6366F1',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.setClipboardData({
|
||||
data: inviteCode,
|
||||
success: () => {
|
||||
uni.showToast({
|
||||
title: '邀请码已复制',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -408,6 +408,51 @@ describe('ContentSecurityService & Compliance Architecture (TDD 微信内容安
|
||||
expect(content).toContain('onWechatChooseAvatar');
|
||||
});
|
||||
|
||||
it('全端 uni.showModal 按钮文字长度严禁超过 4 个汉字 (符合微信官方标准,防止弹窗崩溃)', () => {
|
||||
const walk = (dir: string) => {
|
||||
const files = fs.readdirSync(dir);
|
||||
for (const f of files) {
|
||||
const full = path.join(dir, f);
|
||||
if (f === 'node_modules' || f === 'dist' || f === 'unpackage' || f === '.git') continue;
|
||||
if (fs.statSync(full).isDirectory()) {
|
||||
walk(full);
|
||||
} else if (f.endsWith('.vue') || f.endsWith('.js') || f.endsWith('.ts')) {
|
||||
const content = fs.readFileSync(full, 'utf-8');
|
||||
const modalRegex = /showModal\s*\(\s*\{([\s\S]*?)\}\s*\)/g;
|
||||
let match;
|
||||
while ((match = modalRegex.exec(content)) !== null) {
|
||||
const modalBody = match[1];
|
||||
const confirmMatch = modalBody.match(/confirmText\s*:\s*['"`]([^'"`]+)['"`]/);
|
||||
if (confirmMatch && confirmMatch[1].length > 4) {
|
||||
throw new Error(`Oversized confirmText in ${path.relative(rootDir, full)}: "${confirmMatch[1]}"`);
|
||||
}
|
||||
const cancelMatch = modalBody.match(/cancelText\s*:\s*['"`]([^'"`]+)['"`]/);
|
||||
if (cancelMatch && cancelMatch[1].length > 4) {
|
||||
throw new Error(`Oversized cancelText in ${path.relative(rootDir, full)}: "${cancelMatch[1]}"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
walk(path.join(rootDir, 'pwuniapp'));
|
||||
});
|
||||
|
||||
it('推广赚钱二级分销弹窗必须符合微信4字规范并支持一键复制专属邀请码', () => {
|
||||
const userIndexPath = path.join(rootDir, 'pwuniapp', 'pages', 'user', 'index.vue');
|
||||
const content = fs.readFileSync(userIndexPath, 'utf-8');
|
||||
expect(content).toContain('showInvitePoster');
|
||||
expect(content).toContain('一键复制');
|
||||
expect(content).toContain('setClipboardData');
|
||||
});
|
||||
|
||||
it('首页 Banner 轮播必须具备防 null 链式安全访问与数据容错过滤', () => {
|
||||
const indexVuePath = path.join(rootDir, 'pwuniapp', 'pages', 'index', 'index.vue');
|
||||
const content = fs.readFileSync(indexVuePath, 'utf-8');
|
||||
expect(content).toContain('banner?.bg');
|
||||
expect(content).toContain('banner?.image');
|
||||
expect(content).toContain('banners.value = list.filter(b => b && typeof b === \'object\');');
|
||||
});
|
||||
|
||||
it('所有个人中心改造代码严禁包含任何系统 Emoji', () => {
|
||||
const userIndexPath = path.join(rootDir, 'pwuniapp', 'pages', 'user', 'index.vue');
|
||||
const text = fs.readFileSync(userIndexPath, 'utf-8');
|
||||
|
||||
在新工单中引用
屏蔽一个用户