feat(uniapp_v2): 二开功能迁移与小程序主包优化
- 从 uniapp 迁移 HJF 页面、API、组件及用户/订单相关改动 - queue、assets 使用独立分包以降低主包体积 - 修复首页单根节点与支付结果页 v-if 链 - 关闭 HjfDemoPanel 全局注册;uniNoticeBar 注释 $getAppWebview 避免 __webviewId__ 报错 - 配置域名与 manifest 应用名称;cache/store 防御性处理 Made-with: Cursor
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
# Conversation - 2026-03-10 18:12:21
|
||||
|
||||
## Prompt
|
||||
|
||||
创建页面 history.vue(P13 公排历史页):
|
||||
|
||||
文件路径: /Users/apple/scott2026/huangjingfen/pro_v3.5.1/view/uniapp/pages/queue/history.vue
|
||||
|
||||
内容要求:
|
||||
1. 使用 Vue 2 Options API
|
||||
2. 导入 getQueueHistory from '@/api/hjfQueue.js'
|
||||
3. data: list(Array), page(Number), loading(Boolean), finished(Boolean)
|
||||
4. onLoad + onReachBottom: 加载和分页
|
||||
5. 模板: 按时间分组显示退款记录列表
|
||||
6. 样式: <style scoped lang="scss">
|
||||
7. 参考: docs/frontend-new-pages-spec.md 第 2.3.2 节
|
||||
|
||||
完整 JSDoc。
|
||||
|
||||
## Response
|
||||
|
||||
文件已创建。下面是关键实现点的说明:
|
||||
|
||||
**`history.vue` — P13 公排历史页**
|
||||
|
||||
**数据层**
|
||||
- `list` / `page` / `loading` / `finished` 四个核心状态
|
||||
- `limit: 15`,与 Mock 数据的分页规格一致
|
||||
- `timeKeys` 辅助字段用于历史版本兼容(当前分组逻辑已移入 computed)
|
||||
|
||||
**分组逻辑(computed `groupedList`)**
|
||||
- 先按 `activeTab` 过滤(0=全部,1=`status===0`,2=`status===1`)
|
||||
- 再按 `time_key` 字段聚合,保持原始顺序,返回 `[{ date, children[] }]`
|
||||
|
||||
**生命周期**
|
||||
- `onLoad` → 首次加载
|
||||
- `onReachBottom` → 触底分页,`loading` / `finished` 双重守卫防止重复请求
|
||||
|
||||
**`changeTab`**
|
||||
- 切换 Tab 时重置 `list / page / finished`,重新请求;Tab 相同则 early return
|
||||
|
||||
**`formatTime`**
|
||||
- 将秒级 Unix 时间戳转为 `YYYY-MM-DD HH:mm`,`timestamp=0` 时返回 `--`
|
||||
|
||||
**样式**
|
||||
- `<style scoped lang="scss">`,全部使用 `rpx`
|
||||
- Tab 激活指示器复用 `user_bill` 的 `::after` 伪元素模式
|
||||
- 状态标签:排队中绿色胶囊,已退款灰色胶囊
|
||||
- `sticky` Tab 导航,滚动时悬浮在顶部
|
||||
@@ -0,0 +1,39 @@
|
||||
# Conversation - 2026-03-10 18:13:41
|
||||
|
||||
## Prompt
|
||||
|
||||
创建页面 rules.vue(P14 公排规则页):
|
||||
|
||||
文件路径: /Users/apple/scott2026/huangjingfen/pro_v3.5.1/view/uniapp/pages/queue/rules.vue
|
||||
|
||||
内容要求:
|
||||
1. 使用 Vue 2 Options API
|
||||
2. 静态展示页面,无需 API 调用
|
||||
3. 模板: 公排规则说明(进4退1机制说明、示例计算)
|
||||
4. 参考: docs/frontend-new-pages-spec.md 第 2.3.3 节
|
||||
|
||||
完整 JSDoc。
|
||||
|
||||
## Response
|
||||
|
||||
`rules.vue` 已创建完成。以下是文件内容概览:
|
||||
|
||||
**文件路径**:`pro_v3.5.1/view/uniapp/pages/queue/rules.vue`
|
||||
|
||||
**三大区块**:
|
||||
|
||||
1. **公排机制图示**(`section`)
|
||||
- 进四退一流程图:5个步骤节点(4个"进"步骤 + 1个触发退款的"退"步骤),带颜色区分(红色入队圆标、绿色退款圆标)
|
||||
- 触发节点有橙色"触发退款!"角标
|
||||
- 示例计算卡片:报单 ¥3,600 → 退款 ¥3,600(全额),平均等待约3单
|
||||
|
||||
2. **规则条款列表**(7条)
|
||||
- 带序号圆圈(主题色背景)
|
||||
- 覆盖:全额退款保障、进四退一机制、退款自动到账、入队资格、多单独立排队、积分叠加、规则变更通知
|
||||
|
||||
3. **FAQ 手风琴**(7条)
|
||||
- 点击 `toggleFaq(index)` 切换 `open` 状态
|
||||
- 箭头 `›` 旋转动画(`rotate(90deg)` ↔ `rotate(-90deg)`)
|
||||
- 问题覆盖:等待时长、退款去向、多次参与、循环入队、序号说明、积分叠加、查看进度
|
||||
|
||||
**技术规范符合**:Vue 2 Options API、`<style scoped lang="scss">`、`rpx` 单位、`var(--view-theme)` 主题色、完整 JSDoc 注释(`@file`、`@description`、`@type`、`@param`)。
|
||||
BIN
pro_v3.5.1/view/uniapp_v2/pages/queue/.cursor-cli/sessions.db
Normal file
BIN
pro_v3.5.1/view/uniapp_v2/pages/queue/.cursor-cli/sessions.db
Normal file
Binary file not shown.
373
pro_v3.5.1/view/uniapp_v2/pages/queue/history.vue
Normal file
373
pro_v3.5.1/view/uniapp_v2/pages/queue/history.vue
Normal file
@@ -0,0 +1,373 @@
|
||||
<template>
|
||||
<view :style="colorStyle" class="queue-history">
|
||||
<!-- Tab 筛选 -->
|
||||
<view class="nav acea-row">
|
||||
<view
|
||||
class="item"
|
||||
:class="activeTab === 0 ? 'on' : ''"
|
||||
@click="changeTab(0)"
|
||||
>全部</view>
|
||||
<view
|
||||
class="item"
|
||||
:class="activeTab === 1 ? 'on' : ''"
|
||||
@click="changeTab(1)"
|
||||
>排队中</view>
|
||||
<view
|
||||
class="item"
|
||||
:class="activeTab === 2 ? 'on' : ''"
|
||||
@click="changeTab(2)"
|
||||
>已退款</view>
|
||||
</view>
|
||||
|
||||
<!-- 按日期分组的记录列表 -->
|
||||
<view class="record-list">
|
||||
<view
|
||||
class="date-group"
|
||||
v-for="(group, gIndex) in groupedList"
|
||||
:key="gIndex"
|
||||
>
|
||||
<view class="date-label">{{ group.date }}</view>
|
||||
<view class="group-card">
|
||||
<view
|
||||
class="record-item acea-row row-between-wrapper"
|
||||
v-for="(item, iIndex) in group.children"
|
||||
:key="iIndex"
|
||||
>
|
||||
<!-- 左侧:订单号 + 批次号 -->
|
||||
<view class="record-left">
|
||||
<view class="order-id line1">订单号:{{ item.order_id }}</view>
|
||||
<view class="record-meta">
|
||||
<text v-if="item.trigger_batch > 0">批次号:#{{ item.trigger_batch }}</text>
|
||||
<text v-else>入队序号:#{{ item.queue_no }}</text>
|
||||
</view>
|
||||
<view class="record-time" v-if="item.status === 1 && item.refund_time > 0">
|
||||
退款时间:{{ formatTime(item.refund_time) }}
|
||||
</view>
|
||||
<view class="record-time" v-else>
|
||||
入队时间:{{ formatTime(item.add_time) }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 右侧:金额 + 状态标签 -->
|
||||
<view class="record-right">
|
||||
<view class="amount">¥{{ Number(item.amount).toFixed(2) }}</view>
|
||||
<view class="status-tag" :class="item.status === 1 ? 'refunded' : 'queuing'">
|
||||
{{ item.status === 1 ? '已退款' : '排队中' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-bar acea-row row-center-wrapper" v-if="list.length > 0">
|
||||
<text class="loading iconfont icon-jiazai" :hidden="!loading"></text>
|
||||
{{ loadTitle }}
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="px-20 mt-20" v-if="list.length === 0 && !loading">
|
||||
<emptyPage title="暂无公排记录~" src="/statics/images/noOrder.gif"></emptyPage>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getQueueHistory } from '@/api/hjfQueue.js';
|
||||
import emptyPage from '@/components/emptyPage.vue';
|
||||
import colors from '@/mixins/color';
|
||||
|
||||
/**
|
||||
* P13 公排历史记录页
|
||||
*
|
||||
* 展示当前用户的所有公排历史,支持 Tab 筛选(全部/排队中/已退款),
|
||||
* 记录按日期分组显示,支持上拉翻页加载。
|
||||
*
|
||||
* @see docs/frontend-new-pages-spec.md 2.2.5
|
||||
*/
|
||||
export default {
|
||||
name: 'QueueHistory',
|
||||
|
||||
components: { emptyPage },
|
||||
|
||||
mixins: [colors],
|
||||
|
||||
data() {
|
||||
return {
|
||||
/** @type {Array<Object>} 原始记录列表(所有已加载页的合并) */
|
||||
list: [],
|
||||
|
||||
/** @type {number} 当前页码(从 1 开始) */
|
||||
page: 1,
|
||||
|
||||
/** @type {number} 每页条数 */
|
||||
limit: 15,
|
||||
|
||||
/** @type {boolean} 是否正在加载 */
|
||||
loading: false,
|
||||
|
||||
/** @type {boolean} 是否已加载完全部数据 */
|
||||
finished: false,
|
||||
|
||||
/** @type {string} 底部加载提示文字 */
|
||||
loadTitle: '加载更多',
|
||||
|
||||
/**
|
||||
* 当前激活的 Tab
|
||||
* 0 = 全部,1 = 排队中,2 = 已退款
|
||||
* @type {number}
|
||||
*/
|
||||
activeTab: 0,
|
||||
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
/**
|
||||
* 按 time_key(日期字符串)将 list 分组
|
||||
* 过滤规则:activeTab=0 不过滤;1 只显示 status=0;2 只显示 status=1
|
||||
* @returns {Array<{ date: string, children: Object[] }>}
|
||||
*/
|
||||
groupedList() {
|
||||
const filtered = this.list.filter(item => {
|
||||
if (this.activeTab === 1) return item.status === 0;
|
||||
if (this.activeTab === 2) return item.status === 1;
|
||||
return true;
|
||||
});
|
||||
|
||||
const map = {};
|
||||
const order = [];
|
||||
filtered.forEach(item => {
|
||||
const key = item.time_key;
|
||||
if (!map[key]) {
|
||||
map[key] = [];
|
||||
order.push(key);
|
||||
}
|
||||
map[key].push(item);
|
||||
});
|
||||
|
||||
return order.map(date => ({ date, children: map[date] }));
|
||||
},
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadHistory();
|
||||
},
|
||||
|
||||
onReachBottom() {
|
||||
this.loadHistory();
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* 加载公排历史记录(分页追加)
|
||||
* 若正在加载或已全部加载则直接返回。
|
||||
* @returns {void}
|
||||
*/
|
||||
loadHistory() {
|
||||
if (this.loading || this.finished) return;
|
||||
|
||||
this.loading = true;
|
||||
this.loadTitle = '';
|
||||
|
||||
getQueueHistory({
|
||||
page: this.page,
|
||||
limit: this.limit,
|
||||
status: this.activeTab === 0 ? undefined : this.activeTab === 1 ? 0 : 1,
|
||||
})
|
||||
.then(res => {
|
||||
const newItems = (res.data && res.data.list) ? res.data.list : [];
|
||||
|
||||
newItems.forEach(item => {
|
||||
this.list.push(item);
|
||||
});
|
||||
|
||||
const isEnd = newItems.length < this.limit;
|
||||
this.finished = isEnd;
|
||||
this.loadTitle = isEnd ? '没有更多内容啦~' : '加载更多';
|
||||
this.page += 1;
|
||||
})
|
||||
.catch(() => {
|
||||
this.loadTitle = '加载更多';
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 切换 Tab 筛选,重置列表并重新加载
|
||||
* @param {number} tab - 目标 Tab 索引(0/1/2)
|
||||
* @returns {void}
|
||||
*/
|
||||
changeTab(tab) {
|
||||
if (tab === this.activeTab) return;
|
||||
this.activeTab = tab;
|
||||
this.list = [];
|
||||
this.page = 1;
|
||||
this.finished = false;
|
||||
this.loadHistory();
|
||||
},
|
||||
|
||||
/**
|
||||
* 将 Unix 时间戳格式化为 YYYY-MM-DD HH:mm
|
||||
* @param {number} timestamp - Unix 秒级时间戳
|
||||
* @returns {string} 格式化后的时间字符串
|
||||
*/
|
||||
formatTime(timestamp) {
|
||||
if (!timestamp) return '--';
|
||||
const d = new Date(timestamp * 1000);
|
||||
const pad = n => String(n).padStart(2, '0');
|
||||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.queue-history {
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
/* ===== Tab 导航 ===== */
|
||||
.nav {
|
||||
background: #fff;
|
||||
height: 88rpx;
|
||||
width: 100%;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
|
||||
.item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
line-height: 88rpx;
|
||||
position: relative;
|
||||
|
||||
&.on {
|
||||
color: var(--view-theme);
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
content: ' ';
|
||||
width: 64rpx;
|
||||
height: 6rpx;
|
||||
border-radius: 20rpx;
|
||||
background: var(--view-theme);
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
margin-left: -32rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 记录列表 ===== */
|
||||
.record-list {
|
||||
padding: 24rpx 24rpx 40rpx;
|
||||
}
|
||||
|
||||
.date-group {
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.date-label {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-bottom: 12rpx;
|
||||
padding-left: 4rpx;
|
||||
}
|
||||
|
||||
.group-card {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ===== 单条记录 ===== */
|
||||
.record-item {
|
||||
padding: 28rpx 30rpx;
|
||||
align-items: center;
|
||||
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1rpx solid #f2f2f2;
|
||||
}
|
||||
}
|
||||
|
||||
.record-left {
|
||||
flex: 1;
|
||||
margin-right: 24rpx;
|
||||
|
||||
.order-id {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.record-meta {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
|
||||
.record-time {
|
||||
font-size: 22rpx;
|
||||
color: #bbb;
|
||||
}
|
||||
}
|
||||
|
||||
.record-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
flex-shrink: 0;
|
||||
|
||||
.amount {
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 状态标签 ===== */
|
||||
.status-tag {
|
||||
font-size: 22rpx;
|
||||
padding: 4rpx 14rpx;
|
||||
border-radius: 20rpx;
|
||||
|
||||
&.queuing {
|
||||
color: #19be6b;
|
||||
background: rgba(25, 190, 107, 0.1);
|
||||
}
|
||||
|
||||
&.refunded {
|
||||
color: #999;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 底部加载提示 ===== */
|
||||
.loading-bar {
|
||||
font-size: 26rpx;
|
||||
color: #aaa;
|
||||
padding: 24rpx 0;
|
||||
|
||||
.loading {
|
||||
margin-right: 8rpx;
|
||||
animation: rotate 1s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
531
pro_v3.5.1/view/uniapp_v2/pages/queue/rules.vue
Normal file
531
pro_v3.5.1/view/uniapp_v2/pages/queue/rules.vue
Normal file
@@ -0,0 +1,531 @@
|
||||
<template>
|
||||
<view :style="colorStyle" class="queue-rules">
|
||||
|
||||
<!-- 推荐佣金流程图示 -->
|
||||
<view class="section">
|
||||
<view class="section-title">推荐佣金流程</view>
|
||||
<view class="mechanism-card">
|
||||
<view class="mechanism-title">推荐3人周期佣金流程</view>
|
||||
<view class="flow-diagram">
|
||||
<!-- 第一步 -->
|
||||
<view class="flow-step">
|
||||
<view class="step-circle step-in">推</view>
|
||||
<view class="step-desc">您邀请用户A 购买报单商品</view>
|
||||
<view class="step-sub">您获得 20% 佣金(第1位比例)</view>
|
||||
</view>
|
||||
<view class="flow-arrow">↓</view>
|
||||
<!-- 第二步 -->
|
||||
<view class="flow-step">
|
||||
<view class="step-circle step-in">推</view>
|
||||
<view class="step-desc">您邀请用户B 购买报单商品</view>
|
||||
<view class="step-sub">您获得 30% 佣金(第2位比例)</view>
|
||||
</view>
|
||||
<view class="flow-arrow">↓</view>
|
||||
<!-- 第三步触发完整周期 -->
|
||||
<view class="flow-step trigger">
|
||||
<view class="step-circle step-in">推</view>
|
||||
<view class="step-desc">您邀请用户C 购买报单商品</view>
|
||||
<view class="step-sub">您获得 50% 佣金(第3位比例)</view>
|
||||
<view class="trigger-badge">完成一个周期!</view>
|
||||
</view>
|
||||
<view class="flow-arrow refund-arrow">↓</view>
|
||||
<!-- 新周期 -->
|
||||
<view class="flow-step refund-step">
|
||||
<view class="step-circle step-out">循</view>
|
||||
<view class="step-desc">进入下一推荐周期</view>
|
||||
<view class="step-sub">重新从 20% 开始计算</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 循环说明 -->
|
||||
<view class="cycle-note">
|
||||
<text class="cycle-icon">🔄</text>
|
||||
<text class="cycle-text">如此循环:每推荐3人为一个周期,按比例获得佣金</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 示例计算卡片 -->
|
||||
<view class="example-card">
|
||||
<view class="example-title">示例计算(报单商品 ¥3,600)</view>
|
||||
<view class="example-row">
|
||||
<text class="example-label">推荐第1人佣金(20%)</text>
|
||||
<text class="example-value">¥720.00</text>
|
||||
</view>
|
||||
<view class="example-row">
|
||||
<text class="example-label">推荐第2人佣金(30%)</text>
|
||||
<text class="example-value">¥1,080.00</text>
|
||||
</view>
|
||||
<view class="example-row">
|
||||
<text class="example-label">推荐第3人佣金(50%)</text>
|
||||
<text class="example-value highlight">¥1,800.00</text>
|
||||
</view>
|
||||
<view class="example-divider"></view>
|
||||
<view class="example-row">
|
||||
<text class="example-label">一个周期累计佣金</text>
|
||||
<text class="example-value highlight">¥3,600.00(100%)</text>
|
||||
</view>
|
||||
<view class="example-divider"></view>
|
||||
<view class="example-desc">
|
||||
佣金将在被推荐人支付订单后直接发放至您的佣金余额,可随时申请提现(手续费7%)。
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 规则条款列表 -->
|
||||
<view class="section">
|
||||
<view class="section-title">规则条款</view>
|
||||
<view class="rules-card">
|
||||
<view
|
||||
class="rule-item"
|
||||
v-for="(rule, index) in ruleItems"
|
||||
:key="index"
|
||||
>
|
||||
<view class="rule-index">{{ index + 1 }}</view>
|
||||
<view class="rule-content">
|
||||
<view class="rule-title-text">{{ rule.title }}</view>
|
||||
<view class="rule-body">{{ rule.content }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 常见问题 FAQ 手风琴 -->
|
||||
<view class="section">
|
||||
<view class="section-title">常见问题</view>
|
||||
<view class="faq-list">
|
||||
<view
|
||||
class="faq-item"
|
||||
v-for="(item, index) in faqItems"
|
||||
:key="index"
|
||||
@click="toggleFaq(index)"
|
||||
>
|
||||
<view class="faq-header acea-row row-between-wrapper">
|
||||
<view class="faq-question">{{ item.question }}</view>
|
||||
<view class="faq-arrow" :class="{ 'arrow-up': item.open }">›</view>
|
||||
</view>
|
||||
<view class="faq-answer" v-if="item.open">{{ item.answer }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部声明 -->
|
||||
<view class="footer-note">
|
||||
<text>本平台推荐佣金规则最终解释权归范氏国香商城所有</text>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* @file pages/queue/rules.vue
|
||||
* @description P14 公排规则说明页 — 静态展示页面,无 API 调用
|
||||
*
|
||||
* 页面结构:
|
||||
* 1. 公排机制图示(进四退一流程图 + 示例计算)
|
||||
* 2. 规则条款列表(编号条款)
|
||||
* 3. 常见问题 FAQ 手风琴(点击展开/收起)
|
||||
*
|
||||
* @module pages/queue/rules
|
||||
* @requires mixin/color.js — 提供 colorStyle 计算属性(主题色 CSS 变量注入)
|
||||
*/
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'QueueRules',
|
||||
|
||||
data() {
|
||||
return {
|
||||
/**
|
||||
* @type {Array<{title: string, content: string}>}
|
||||
* @description 规则条款列表,静态数据
|
||||
*/
|
||||
ruleItems: [
|
||||
{
|
||||
title: '推荐佣金机制',
|
||||
content: '购买报单商品(¥3,600)后,当您推荐好友购买时,您可获得对应周期位次的佣金比例(20%/30%/50%)。'
|
||||
},
|
||||
{
|
||||
title: '3人周期循环',
|
||||
content: '每推荐3人为一个完整周期,依次按第1人20%、第2人30%、第3人50%发放佣金。周期结束后自动进入下一轮。'
|
||||
},
|
||||
{
|
||||
title: '佣金自动到账',
|
||||
content: '佣金将在被推荐人支付订单后直接发放至您的佣金余额,无需手动申请,可在"推荐佣金"页查看。'
|
||||
},
|
||||
{
|
||||
title: '报单商品范围',
|
||||
content: '仅购买标注为"报单商品"的商品才参与佣金计算,普通商品按常规佣金比例计算。'
|
||||
},
|
||||
{
|
||||
title: '推荐关系绑定',
|
||||
content: '被推荐人须通过您的专属邀请链接/二维码注册并购买,方可绑定推荐关系并产生佣金。'
|
||||
},
|
||||
{
|
||||
title: '积分奖励叠加',
|
||||
content: '推荐佣金与积分奖励体系相互独立,获得佣金的同时可正常获得待释放积分奖励,每日自动释放0.4‰到可用积分。'
|
||||
},
|
||||
{
|
||||
title: '规则变更通知',
|
||||
content: '若平台对佣金规则进行调整,将提前通过公告及消息通知用户,变更后的规则不溯及既往订单。'
|
||||
}
|
||||
],
|
||||
|
||||
/**
|
||||
* @type {Array<{question: string, answer: string, open: boolean}>}
|
||||
* @description 常见问题列表,open 字段控制手风琴展开状态
|
||||
*/
|
||||
faqItems: [
|
||||
{
|
||||
question: '推荐一个人能得多少佣金?',
|
||||
answer: '取决于您在当前周期的位次。第1人20%,第2人30%,第3人50%。以报单商品¥3,600为例,分别是¥720、¥1,080、¥1,800。',
|
||||
open: false
|
||||
},
|
||||
{
|
||||
question: '佣金会到哪里?',
|
||||
answer: '佣金将发放至您的佣金余额,可在"推荐佣金"页查看,并可随时申请提现(提现手续费7%)。',
|
||||
open: false
|
||||
},
|
||||
{
|
||||
question: '推荐超过3人怎么计算?',
|
||||
answer: '每满3人为一个周期,自动进入下一周期重新从第1位(20%)开始计算。如推荐第4人,其佣金比例与第1人相同(20%)。',
|
||||
open: false
|
||||
},
|
||||
{
|
||||
question: '我没有购买报单商品可以推荐别人吗?',
|
||||
answer: '可以推荐,但获得佣金需要您有对应的分销等级。建议先购买报单商品成为会员,再邀请好友。',
|
||||
open: false
|
||||
},
|
||||
{
|
||||
question: '待释放积分是什么?',
|
||||
answer: '每次获得佣金时,同步获得等额待释放积分(1元佣金=100积分)。积分每天自动释放0.4‰到可用积分,可用于平台消费抵扣。',
|
||||
open: false
|
||||
},
|
||||
{
|
||||
question: '佣金和积分可以同时获得吗?',
|
||||
answer: '可以。推荐好友购买报单商品后,您同时获得佣金和待释放积分,两套机制并行运作,互不影响。',
|
||||
open: false
|
||||
},
|
||||
{
|
||||
question: '如何查看我的佣金进度?',
|
||||
answer: '进入"推荐佣金"页可查看:当前周期推荐进度(X/3)、各档比例、累计佣金、佣金记录明细。',
|
||||
open: false
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(['colorStyle'])
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* @description 切换 FAQ 手风琴展开/收起状态
|
||||
* @param {number} index - FAQ 条目的索引
|
||||
*/
|
||||
toggleFaq(index) {
|
||||
this.faqItems[index].open = !this.faqItems[index].open;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.queue-rules {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
/* ===== 通用区块 ===== */
|
||||
.section {
|
||||
margin: 20rpx 24rpx 0;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #282828;
|
||||
padding: 24rpx 0 16rpx;
|
||||
}
|
||||
|
||||
/* ===== 机制图示卡片 ===== */
|
||||
.mechanism-card {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx 28rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.mechanism-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: var(--view-theme, #e93323);
|
||||
text-align: center;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
/* 流程图 */
|
||||
.flow-diagram {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.flow-step {
|
||||
width: 100%;
|
||||
background: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 18rpx 24rpx;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
|
||||
&.trigger {
|
||||
background: #fff7f0;
|
||||
border: 2rpx solid #ff9d4d;
|
||||
}
|
||||
|
||||
&.refund-step {
|
||||
background: #f0fff4;
|
||||
border: 2rpx solid #52c41a;
|
||||
}
|
||||
}
|
||||
|
||||
.step-circle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 52rpx;
|
||||
height: 52rpx;
|
||||
border-radius: 50%;
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
margin-bottom: 10rpx;
|
||||
|
||||
&.step-in {
|
||||
background: var(--view-theme, #e93323);
|
||||
}
|
||||
|
||||
&.step-out {
|
||||
background: #52c41a;
|
||||
}
|
||||
}
|
||||
|
||||
.step-desc {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.step-sub {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
|
||||
.trigger-badge {
|
||||
display: inline-block;
|
||||
background: #ff9d4d;
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
border-radius: 20rpx;
|
||||
padding: 4rpx 16rpx;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.flow-arrow {
|
||||
font-size: 36rpx;
|
||||
color: #ccc;
|
||||
line-height: 50rpx;
|
||||
text-align: center;
|
||||
|
||||
&.refund-arrow {
|
||||
color: #52c41a;
|
||||
}
|
||||
}
|
||||
|
||||
/* 循环说明 */
|
||||
.cycle-note {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 24rpx;
|
||||
padding: 16rpx 20rpx;
|
||||
background: #f0f4ff;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
.cycle-icon {
|
||||
font-size: 28rpx;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.cycle-text {
|
||||
font-size: 24rpx;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
/* 示例计算卡片 */
|
||||
.example-card {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 28rpx;
|
||||
margin-top: 20rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.example-title {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
color: #282828;
|
||||
margin-bottom: 18rpx;
|
||||
}
|
||||
|
||||
.example-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10rpx 0;
|
||||
}
|
||||
|
||||
.example-label {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.example-value {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
|
||||
&.highlight {
|
||||
color: #52c41a;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
.example-divider {
|
||||
height: 1rpx;
|
||||
background: #f0f0f0;
|
||||
margin: 16rpx 0;
|
||||
}
|
||||
|
||||
.example-desc {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
/* ===== 规则条款 ===== */
|
||||
.rules-card {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 10rpx 28rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.rule-item {
|
||||
display: flex;
|
||||
padding: 24rpx 0;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.rule-index {
|
||||
flex-shrink: 0;
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
border-radius: 50%;
|
||||
background: var(--view-theme, #e93323);
|
||||
color: #fff;
|
||||
font-size: 22rpx;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20rpx;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.rule-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.rule-title-text {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.rule-body {
|
||||
font-size: 24rpx;
|
||||
color: #777;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* ===== FAQ 手风琴 ===== */
|
||||
.faq-list {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.faq-item {
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.faq-header {
|
||||
padding: 28rpx;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.faq-question {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
padding-right: 20rpx;
|
||||
}
|
||||
|
||||
.faq-arrow {
|
||||
flex-shrink: 0;
|
||||
font-size: 40rpx;
|
||||
color: #ccc;
|
||||
transform: rotate(90deg);
|
||||
transition: transform 0.25s ease;
|
||||
line-height: 1;
|
||||
|
||||
&.arrow-up {
|
||||
transform: rotate(-90deg);
|
||||
color: var(--view-theme, #e93323);
|
||||
}
|
||||
}
|
||||
|
||||
.faq-answer {
|
||||
padding: 0 28rpx 24rpx;
|
||||
font-size: 24rpx;
|
||||
color: #888;
|
||||
line-height: 1.8;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
/* ===== 底部声明 ===== */
|
||||
.footer-note {
|
||||
margin: 30rpx 24rpx 0;
|
||||
text-align: center;
|
||||
font-size: 22rpx;
|
||||
color: #bbb;
|
||||
}
|
||||
</style>
|
||||
249
pro_v3.5.1/view/uniapp_v2/pages/queue/status.vue
Normal file
249
pro_v3.5.1/view/uniapp_v2/pages/queue/status.vue
Normal file
@@ -0,0 +1,249 @@
|
||||
<template>
|
||||
<view class="brokerage-page" :style="colorStyle">
|
||||
<!-- 顶部渐变区域 -->
|
||||
<view class="header-gradient">
|
||||
<view class="header-gradient__circle header-gradient__circle--1"></view>
|
||||
<view class="header-gradient__circle header-gradient__circle--2"></view>
|
||||
|
||||
<view class="header-card">
|
||||
<view class="header-card__label">累计佣金收入</view>
|
||||
<view class="header-card__amount">
|
||||
<text class="header-card__currency">¥</text>
|
||||
<text class="header-card__value">{{ progressData.total_brokerage || '0.00' }}</text>
|
||||
</view>
|
||||
<!-- 佣金周期进度环 -->
|
||||
<HjfQueueProgress
|
||||
:cycle-current="progressData.cycle_current || 0"
|
||||
:cycle-total="progressData.cycle_total || 3"
|
||||
:cycle-rates="progressData.cycle_rates || [20, 30, 50]"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 佣金记录列表 -->
|
||||
<view class="records-section">
|
||||
<view class="section-header">
|
||||
<view class="section-header__bar"></view>
|
||||
<text class="section-header__title">佣金记录</text>
|
||||
<text class="section-header__more" @tap="goToCommissionDetail">查看全部</text>
|
||||
</view>
|
||||
|
||||
<view v-if="loading" class="loading-wrap">
|
||||
<view class="loading-dot loading-dot--1"></view>
|
||||
<view class="loading-dot loading-dot--2"></view>
|
||||
<view class="loading-dot loading-dot--3"></view>
|
||||
</view>
|
||||
|
||||
<view v-else-if="records.length === 0" class="empty-wrap">
|
||||
<emptyPage title="暂无佣金记录" src="/statics/images/noOrder.gif" />
|
||||
</view>
|
||||
|
||||
<view v-else class="record-list">
|
||||
<view
|
||||
v-for="(item, index) in records"
|
||||
:key="item.id || index"
|
||||
class="record-item"
|
||||
>
|
||||
<view class="record-item__left">
|
||||
<view class="record-item__avatar-wrap">
|
||||
<image
|
||||
class="record-item__avatar"
|
||||
:src="item.avatar || '/statics/images/avatar.png'"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
</view>
|
||||
<view class="record-item__info">
|
||||
<text class="record-item__name">{{ item.nickname || '用户' }}</text>
|
||||
<text class="record-item__time">{{ item.time }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="record-item__right">
|
||||
<text class="record-item__amount">+¥{{ item.number }}</text>
|
||||
<text class="record-item__type">{{ item.title || '推荐佣金' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 上拉加载 -->
|
||||
<view v-if="records.length > 0" class="load-more">
|
||||
<text v-if="loadingMore" class="load-more__text">加载中...</text>
|
||||
<text v-else-if="finished" class="load-more__text">—— 没有更多了 ——</text>
|
||||
</view>
|
||||
|
||||
<!-- 佣金到账通知 -->
|
||||
<HjfRefundNotice
|
||||
:visible="showNotice"
|
||||
:amount="noticeData.amount"
|
||||
:order-id="noticeData.orderId"
|
||||
@close="showNotice = false"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getBrokerageProgress } from '@/api/hjfQueue.js';
|
||||
import HjfQueueProgress from '@/components/HjfQueueProgress.vue';
|
||||
import HjfRefundNotice from '@/components/HjfRefundNotice.vue';
|
||||
import emptyPage from '@/components/emptyPage.vue';
|
||||
import colors from '@/mixins/color.js';
|
||||
import { spreadOrder } from '@/api/user.js';
|
||||
|
||||
export default {
|
||||
name: 'BrokerageStatus',
|
||||
mixins: [colors],
|
||||
components: { HjfQueueProgress, HjfRefundNotice, emptyPage },
|
||||
|
||||
data() {
|
||||
return {
|
||||
progressData: {},
|
||||
records: [],
|
||||
loading: false,
|
||||
loadingMore: false,
|
||||
finished: false,
|
||||
page: 1,
|
||||
limit: 15,
|
||||
showNotice: false,
|
||||
noticeData: { amount: 0, orderId: '' }
|
||||
};
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadProgress();
|
||||
this.loadRecords();
|
||||
},
|
||||
|
||||
onReachBottom() {
|
||||
if (!this.loadingMore && !this.finished) this.loadRecords();
|
||||
},
|
||||
|
||||
methods: {
|
||||
loadProgress() {
|
||||
getBrokerageProgress()
|
||||
.then(res => {
|
||||
if (res && res.data) this.progressData = res.data;
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
|
||||
loadRecords() {
|
||||
if (this.loading || this.loadingMore || this.finished) return;
|
||||
const isFirst = this.page === 1;
|
||||
if (isFirst) this.loading = true; else this.loadingMore = true;
|
||||
|
||||
spreadOrder({ page: this.page, limit: this.limit, category: 'now_money', type: 'brokerage' })
|
||||
.then(res => {
|
||||
const list = (res && res.data && res.data.list) ? res.data.list : [];
|
||||
if (list.length < this.limit) this.finished = true;
|
||||
if (isFirst) this.records = list;
|
||||
else this.records = [...this.records, ...list];
|
||||
this.page++;
|
||||
})
|
||||
.catch(() => {
|
||||
if (isFirst) this.records = [];
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
this.loadingMore = false;
|
||||
});
|
||||
},
|
||||
|
||||
goToCommissionDetail() {
|
||||
uni.navigateTo({ url: '/pages/users/user_spread_money/index' });
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.brokerage-page {
|
||||
min-height: 100vh;
|
||||
background: #f4f5f7;
|
||||
padding-bottom: 60rpx;
|
||||
}
|
||||
|
||||
.header-gradient {
|
||||
background: linear-gradient(135deg, var(--view-theme, #e93323) 0%, var(--view-gradient, #f76b1c) 100%);
|
||||
padding: 40rpx 30rpx 56rpx;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.header-gradient__circle {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
opacity: 0.07;
|
||||
}
|
||||
.header-gradient__circle--1 { width: 400rpx; height: 400rpx; top: -150rpx; right: -80rpx; }
|
||||
.header-gradient__circle--2 { width: 240rpx; height: 240rpx; bottom: -60rpx; left: -50rpx; }
|
||||
|
||||
.header-card {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
background: rgba(255,255,255,0.14);
|
||||
border-radius: 28rpx;
|
||||
padding: 36rpx 32rpx;
|
||||
}
|
||||
.header-card__label { font-size: 26rpx; color: rgba(255,255,255,0.8); margin-bottom: 8rpx; }
|
||||
.header-card__amount { display: flex; align-items: baseline; margin-bottom: 28rpx; }
|
||||
.header-card__currency { font-size: 36rpx; color: #fff; font-weight: 600; margin-right: 4rpx; }
|
||||
.header-card__value { font-size: 72rpx; font-weight: 700; color: #fff; line-height: 1; }
|
||||
|
||||
.records-section {
|
||||
margin: -16rpx 20rpx 0;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 0 0 20rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4rpx 24rpx rgba(0,0,0,0.06);
|
||||
}
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 28rpx 30rpx 20rpx;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
}
|
||||
.section-header__bar {
|
||||
width: 6rpx; height: 28rpx; border-radius: 3rpx;
|
||||
background: var(--view-theme, #e93323); margin-right: 14rpx;
|
||||
}
|
||||
.section-header__title { font-size: 30rpx; font-weight: 600; color: #333; flex: 1; }
|
||||
.section-header__more { font-size: 24rpx; color: var(--view-theme, #e93323); }
|
||||
|
||||
.loading-wrap {
|
||||
display: flex; justify-content: center; align-items: center; gap: 12rpx;
|
||||
padding: 60rpx 0;
|
||||
}
|
||||
.loading-dot {
|
||||
width: 14rpx; height: 14rpx; border-radius: 50%;
|
||||
background: var(--view-theme, #e93323);
|
||||
animation: dot-bounce 1.2s infinite ease-in-out;
|
||||
}
|
||||
.loading-dot--2 { animation-delay: 0.2s; }
|
||||
.loading-dot--3 { animation-delay: 0.4s; }
|
||||
@keyframes dot-bounce {
|
||||
0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); }
|
||||
40% { opacity: 1; transform: scale(1.2); }
|
||||
}
|
||||
|
||||
.empty-wrap { padding: 40rpx 0; }
|
||||
|
||||
.record-item {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 28rpx 30rpx;
|
||||
border-bottom: 1rpx solid #f8f8f8;
|
||||
}
|
||||
.record-item__left { display: flex; align-items: center; gap: 20rpx; }
|
||||
.record-item__avatar-wrap { width: 76rpx; height: 76rpx; border-radius: 50%; overflow: hidden; flex-shrink: 0; }
|
||||
.record-item__avatar { width: 100%; height: 100%; }
|
||||
.record-item__name { font-size: 28rpx; color: #333; font-weight: 500; display: block; }
|
||||
.record-item__time { font-size: 22rpx; color: #999; display: block; margin-top: 6rpx; }
|
||||
.record-item__right { text-align: right; }
|
||||
.record-item__amount { font-size: 34rpx; font-weight: 700; color: #e93323; display: block; }
|
||||
.record-item__type { font-size: 22rpx; color: #aaa; display: block; margin-top: 4rpx; }
|
||||
|
||||
.load-more { padding: 32rpx 0 20rpx; text-align: center; }
|
||||
.load-more__text { font-size: 24rpx; color: #bbb; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user