feat(user): replace distribution modal with dedicated promote page

这个提交包含在:
xcbyy
2026-10-13 15:17:15 +08:00
父节点 831dd4a493
当前提交 7a5e515b8b
共有 4 个文件被更改,包括 1347 次插入 和 203 次删除

查看文件

@@ -152,6 +152,16 @@
"backgroundColor": "#0A0F1D"
}
},
{
"path": "pages/user/promote",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "推广赚钱",
"navigationBarBackgroundColor": "#0A0F1D",
"navigationBarTextStyle": "white",
"backgroundColor": "#0A0F1D"
}
},
{
"path": "pages/user/settings",
"style": {

查看文件

@@ -117,7 +117,7 @@
<text class="menu-title">所有订单</text>
<text class="menu-desc">对局与退款</text>
</view>
<view class="menu-item btn-active" @click="showInvitePoster">
<view class="menu-item btn-active" @click="navTo('/pages/user/promote')">
<view class="menu-icon bg-purple">
<image class="menu-svg" :src="ICONS.share" mode="aspectFit" />
</view>
@@ -321,200 +321,7 @@
</view>
</view>
<!-- 3. 分销推广管理弹窗 -->
<view class="profile-edit-modal-mask" v-if="showDistributionModal" @click="showDistributionModal = false">
<view class="profile-edit-modal dist-modal-box" @click.stop>
<view class="modal-header">
<view class="modal-title-wrap">
<text class="modal-title">分销推广管理</text>
<text class="modal-sub">全网二级裂变 · 下单即时分润</text>
</view>
<view class="btn-close btn-active" @click="showDistributionModal = false">
<image class="close-svg" :src="ICONS.close" mode="aspectFit" />
</view>
</view>
<!-- 顶部佣金总览 -->
<view class="dist-summary-banner">
<view class="dist-stat-col">
<text class="dist-stat-val">¥{{ user.commission || '0.00' }}</text>
<text class="dist-stat-lbl">可提现佣金</text>
</view>
<view class="dist-stat-divider"></view>
<view class="dist-stat-col">
<text class="dist-stat-val">{{ distributionTeam.totalTeamCount || user.invitedCount || 0 }} 人</text>
<text class="dist-stat-lbl">下级团队成员</text>
</view>
<view class="dist-stat-divider"></view>
<view class="dist-stat-col">
<view class="code-box btn-active" @click="copyInviteCode">
<text class="code-txt">PW{{ user.id || 1001 }}</text>
<text class="copy-tag">一键复制</text>
</view>
<text class="dist-stat-lbl">专属邀请码</text>
</view>
</view>
<!-- 选项卡切换 -->
<view class="dist-tabs-row">
<view
class="dist-tab-pill btn-active"
:class="{ active: distActiveTab === 'poster' }"
@click="distActiveTab = 'poster'"
>
推广海报
</view>
<view
class="dist-tab-pill btn-active"
:class="{ active: distActiveTab === 'team' }"
@click="distActiveTab = 'team'"
>
下级团队
</view>
<view
class="dist-tab-pill btn-active"
:class="{ active: distActiveTab === 'records' }"
@click="distActiveTab = 'records'"
>
返佣订单
</view>
<view
class="dist-tab-pill btn-active"
:class="{ active: distActiveTab === 'withdraw' }"
@click="distActiveTab = 'withdraw'"
>
佣金提现
</view>
</view>
<view class="modal-body dist-modal-body">
<!-- Tab 1: 专属太阳码海报 -->
<view class="tab-pane-poster" v-if="distActiveTab === 'poster'">
<view class="poster-preview-card">
<view class="poster-badge">官方认证邀请码</view>
<text class="poster-hero-title">电竞陪伴 · 同城玩乐</text>
<text class="poster-hero-sub">寻找心仪陪伴师,立享新人首单礼包</text>
<!-- 太阳码矢量视觉卡 -->
<view class="suncode-box">
<view class="suncode-ring">
<view class="sun-dot" v-for="d in 12" :key="d"></view>
<view class="suncode-center">
<image class="suncode-avatar" :src="user.avatar || DEFAULT_GUEST_AVATAR" mode="aspectFill" />
</view>
</view>
<text class="suncode-hint">微信长按或扫码进入</text>
</view>
<view class="poster-footer-code">
<text class="lbl">邀请码:</text>
<text class="val">PW{{ user.id || 1001 }}</text>
</view>
</view>
<view class="poster-actions-row">
<button class="btn-poster-action secondary btn-active" @click="copyInviteLink">复制邀请链接</button>
<button class="btn-poster-action primary btn-active" @click="savePosterImage">保存海报至相册</button>
</view>
</view>
<!-- Tab 2: 下级团队列表 -->
<view class="tab-pane-team" v-if="distActiveTab === 'team'">
<view class="team-subtabs">
<view
class="subtab-item btn-active"
:class="{ active: teamLevelTab === 1 }"
@click="teamLevelTab = 1"
>
一级直推 ({{ distributionTeam.directCount || 0 }}人 · 5%)
</view>
<view
class="subtab-item btn-active"
:class="{ active: teamLevelTab === 2 }"
@click="teamLevelTab = 2"
>
二级间推 ({{ distributionTeam.indirectCount || 0 }}人 · 2%)
</view>
</view>
<scroll-view scroll-y class="team-list-scroll">
<view
class="team-member-row"
v-for="m in (teamLevelTab === 1 ? distributionTeam.directList : distributionTeam.indirectList)"
:key="m.id"
>
<image class="m-avatar" :src="m.avatar" mode="aspectFill" />
<view class="m-info">
<view class="m-top">
<text class="m-name">{{ m.nickname }}</text>
<text class="m-level-badge">LV{{ m.level }}好友</text>
</view>
<text class="m-time">加入时间:{{ m.createdAt }}</text>
</view>
<view class="m-contribute">
<text class="m-val">¥{{ m.totalContribute || '0.00' }}</text>
<text class="m-lbl">贡献收益</text>
</view>
</view>
</scroll-view>
</view>
<!-- Tab 3: 返佣订单明细 -->
<view class="tab-pane-records" v-if="distActiveTab === 'records'">
<scroll-view scroll-y class="records-list-scroll">
<view class="record-card" v-for="r in distributionRecords" :key="r.id">
<view class="rec-top">
<text class="rec-sn">{{ r.orderNo }}</text>
<text class="rec-status-tag">{{ r.status }}</text>
</view>
<view class="rec-mid">
<view class="rec-from">
<text class="rec-name">{{ r.fromUserName }}</text>
<text class="rec-type">{{ r.level === 1 ? '直推订单' : '间推订单' }}</text>
</view>
<view class="rec-amount-box">
<text class="rec-comm">+¥{{ r.commission }}</text>
<text class="rec-rate">提成 {{ r.ratePercent }}%</text>
</view>
</view>
<view class="rec-bot">
<text class="rec-time">{{ r.createdAt }}</text>
<text class="rec-ord-fee">消费:¥{{ r.orderAmount }}</text>
</view>
</view>
</scroll-view>
</view>
<!-- Tab 4: 快速佣金提现 -->
<view class="tab-pane-withdraw" v-if="distActiveTab === 'withdraw'">
<view class="dist-wd-box">
<text class="dist-wd-label">提现金额 (元)</text>
<view class="dist-input-row">
<text class="symbol">¥</text>
<input
type="digit"
class="dist-amount-input"
v-model="distWithdrawAmount"
placeholder="请输入提现金额"
placeholder-class="field-placeholder"
/>
<text class="btn-all btn-active" @click="distWithdrawAmount = String(user.commission || 0)">全部提现</text>
</view>
<view class="dist-wd-tip">
<text class="tip-txt">支持自动提现至绑定微信零钱,平台免收手续费,T+0 快速审核到账。</text>
</view>
<button
class="btn-submit-dist-wd btn-active"
:disabled="submittingDistWithdraw"
@click="submitDistWithdraw"
>
{{ submittingDistWithdraw ? '正在提交...' : '确认发起提现' }}
</button>
</view>
</view>
</view>
</view>
</view>
<!-- 底部自定义导航栏 (动态自适应发单人4项 / 接单人3项) -->
<AppTabBar current="user" />
@@ -1136,12 +943,12 @@ const clearLocalCache = () => {
}
};
// 3. 分销推广管理业务逻辑
const openDistributionModal = async () => {
// 3. 分销推广管理业务逻辑 (跳转独立页面)
const openDistributionModal = () => {
if (!ensureLoggedIn()) return;
showDistributionModal.value = true;
distActiveTab.value = 'poster';
await loadDistributionData();
uni.navigateTo({
url: '/pages/user/promote'
});
};
const showInvitePoster = () => {

文件差异内容过多而无法显示 加载差异

查看文件

@@ -130,7 +130,7 @@ describe('ContentSecurityService & Compliance Architecture (TDD 微信内容安
const raw = fs.readFileSync(pagesJsonPath, 'utf-8');
const config = JSON.parse(raw);
expect(Array.isArray(config.pages)).toBe(true);
expect(config.pages.length).toBe(21);
expect(config.pages.length).toBe(22);
for (const pageItem of config.pages) {
const vueFilePath = path.join(rootDir, 'pwuniapp', `${pageItem.path}.vue`);
@@ -437,12 +437,14 @@ describe('ContentSecurityService & Compliance Architecture (TDD 微信内容安
walk(path.join(rootDir, 'pwuniapp'));
});
it('推广赚钱二级分销弹窗必须符合微信4字规范并支持一键复制专属邀请码', () => {
it('推广赚钱二级分销专属页面与功能必须符合微信4字规范并支持一键复制专属邀请码', () => {
const userIndexPath = path.join(rootDir, 'pwuniapp', 'pages', 'user', 'index.vue');
const promotePath = path.join(rootDir, 'pwuniapp', 'pages', 'user', 'promote.vue');
const content = fs.readFileSync(userIndexPath, 'utf-8');
const promoteContent = fs.readFileSync(promotePath, 'utf-8');
expect(content).toContain('showInvitePoster');
expect(content).toContain('一键复制');
expect(content).toContain('setClipboardData');
expect(promoteContent).toContain('一键复制');
expect(promoteContent).toContain('setClipboardData');
});
it('首页 Banner 轮播必须具备防 null 链式安全访问与数据容错过滤', () => {
@@ -877,6 +879,44 @@ describe('ContentSecurityService & Compliance Architecture (TDD 微信内容安
expect(emojiRegex.test(content)).toBe(false);
});
});
describe('20. 独立专属推广赚钱页面规格验证 (pages/user/promote.vue)', () => {
const promotePath = path.join(rootDir, 'pwuniapp', 'pages', 'user', 'promote.vue');
const pagesJsonPath = path.join(rootDir, 'pwuniapp', 'pages.json');
it('pages.json 必须成功注册 pages/user/promote 且物理文件必须存在', () => {
expect(fs.existsSync(promotePath)).toBe(true);
const pagesJson = JSON.parse(fs.readFileSync(pagesJsonPath, 'utf-8'));
const hasPage = pagesJson.pages.some((p: any) => p.path === 'pages/user/promote');
expect(hasPage).toBe(true);
});
it('推广赚钱页面必须包含沉浸式导航栏、分销总览、海报/团队/订单/提现四大板块', () => {
const content = fs.readFileSync(promotePath, 'utf-8');
expect(content).toContain('custom-nav-bar');
expect(content).toContain('推广赚钱');
expect(content).toContain('dist-summary-banner');
expect(content).toContain('tab-pane-poster');
expect(content).toContain('tab-pane-team');
expect(content).toContain('tab-pane-records');
expect(content).toContain('tab-pane-withdraw');
expect(content).toContain('handleBack');
});
it('个人中心页面 (pages/user/index.vue) 推广赚钱入口必须原生跳转至 /pages/user/promote 且杜绝弹窗', () => {
const userIndexPath = path.join(rootDir, 'pwuniapp', 'pages', 'user', 'index.vue');
const content = fs.readFileSync(userIndexPath, 'utf-8');
expect(content).toContain("navTo('/pages/user/promote')");
expect(content).toContain("url: '/pages/user/promote'");
expect(content).not.toContain('class="profile-edit-modal dist-modal-box"');
});
it('推广赚钱页面代码与测试严禁包含任何系统 Emoji 违规字符', () => {
const content = fs.readFileSync(promotePath, 'utf-8');
const emojiRegex = /(?:\p{Extended_Pictographic}|\p{Emoji_Presentation}|[\u2600-\u27BF])/u;
expect(emojiRegex.test(content)).toBe(false);
});
});
});