feat(user): replace distribution modal with dedicated promote page
这个提交包含在:
@@ -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 = () => {
|
||||
|
||||
1287
pwuniapp/pages/user/promote.vue
普通文件
1287
pwuniapp/pages/user/promote.vue
普通文件
文件差异内容过多而无法显示
加载差异
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户