接单平台新增 VIP 等级体系与邀请绑定
- 等级按累计验收通过工单数自动升级(阈值后台可配),VIP1-5 默认配置 10/30/50/100 - 存量打手统一归 VIP1,原 regular 等级停用 - 注册支持邀请码绑定邀请人,每人生成唯一邀请码 - 新增 worker_level_logs 升级流水表 - 个人中心展示邀请码与升级进度,管理端等级表单支持升级阈值配置
This commit is contained in:
@@ -1790,6 +1790,7 @@ function LevelsPanel() {
|
||||
name: string
|
||||
depositFreeAmount?: number
|
||||
maxActiveOrders?: number
|
||||
upgradeThreshold?: number
|
||||
}) {
|
||||
try {
|
||||
await saveAdminWorkerLevel(values)
|
||||
@@ -1810,6 +1811,7 @@ function LevelsPanel() {
|
||||
render: (_, row) => formatMoney(row.depositFreeAmount),
|
||||
},
|
||||
{ title: '最大同时接单', dataIndex: 'maxActiveOrders' },
|
||||
{ title: '升级所需累计完成', dataIndex: 'upgradeThreshold' },
|
||||
{ title: '状态', dataIndex: 'status' },
|
||||
]
|
||||
|
||||
@@ -1820,10 +1822,11 @@ function LevelsPanel() {
|
||||
form={form}
|
||||
layout="inline"
|
||||
initialValues={{
|
||||
levelKey: 'regular',
|
||||
name: '普通打手',
|
||||
levelKey: 'vip1',
|
||||
name: 'VIP1',
|
||||
depositFreeAmount: 0,
|
||||
maxActiveOrders: 3,
|
||||
maxActiveOrders: 1,
|
||||
upgradeThreshold: 0,
|
||||
}}
|
||||
onFinish={saveLevel}
|
||||
>
|
||||
@@ -1847,6 +1850,13 @@ function LevelsPanel() {
|
||||
<Form.Item label="最大同时接单" name="maxActiveOrders">
|
||||
<InputNumber min={1} max={999} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="升级所需累计完成单数"
|
||||
name="upgradeThreshold"
|
||||
tooltip="打手累计验收通过该单数后自动升级到本等级"
|
||||
>
|
||||
<InputNumber min={0} max={100000} />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit">
|
||||
保存
|
||||
|
||||
@@ -34,6 +34,7 @@ export default function WorkerLoginPage() {
|
||||
password: string
|
||||
displayName?: string
|
||||
phone?: string
|
||||
inviteCode?: string
|
||||
}) {
|
||||
setLoading(true)
|
||||
try {
|
||||
@@ -98,6 +99,9 @@ export default function WorkerLoginPage() {
|
||||
<Form.Item label="手机号" name="phone">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="邀请码(选填)" name="inviteCode">
|
||||
<Input maxLength={8} placeholder="填写邀请人邀请码" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="密码"
|
||||
name="password"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
BankOutlined,
|
||||
CopyOutlined,
|
||||
CustomerServiceOutlined,
|
||||
HistoryOutlined,
|
||||
LockOutlined,
|
||||
@@ -23,6 +24,7 @@ import {
|
||||
Input,
|
||||
InputNumber,
|
||||
Modal,
|
||||
Progress,
|
||||
Row,
|
||||
Select,
|
||||
Space,
|
||||
@@ -114,9 +116,20 @@ export default function WorkerProfilePage() {
|
||||
|
||||
const worker = profileQuery.data?.data.worker
|
||||
const summary = profileQuery.data?.data.summary
|
||||
const levelProgress = profileQuery.data?.data.levelProgress
|
||||
const financeConfig = profileQuery.data?.data.financeConfig
|
||||
const canUseFinanceActions = worker?.status === 'active'
|
||||
|
||||
async function copyInviteCode() {
|
||||
if (!worker?.inviteCode) return
|
||||
try {
|
||||
await navigator.clipboard.writeText(worker.inviteCode)
|
||||
message.success('邀请码已复制')
|
||||
} catch {
|
||||
message.error('复制失败,请手动复制')
|
||||
}
|
||||
}
|
||||
|
||||
const ledgersQuery = useQuery({
|
||||
queryKey: ['worker-wallet-ledgers', ledgerType, ledgerPage, ledgerPageSize],
|
||||
queryFn: () =>
|
||||
@@ -453,6 +466,40 @@ export default function WorkerProfilePage() {
|
||||
<Tag>{worker.level?.name || '未分级'}</Tag>
|
||||
</Space>
|
||||
</Space>
|
||||
<Space direction="vertical" size={4} style={{ marginTop: 12, width: '100%' }}>
|
||||
<Space wrap size={[8, 8]}>
|
||||
<Typography.Text type="secondary">
|
||||
我的邀请码:
|
||||
</Typography.Text>
|
||||
<Typography.Text strong>{worker.inviteCode || '-'}</Typography.Text>
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
icon={<CopyOutlined />}
|
||||
disabled={!worker.inviteCode}
|
||||
onClick={copyInviteCode}
|
||||
>
|
||||
复制
|
||||
</Button>
|
||||
</Space>
|
||||
{levelProgress && levelProgress.nextThreshold !== null ? (
|
||||
<Space direction="vertical" size={0} style={{ width: '100%' }}>
|
||||
<Typography.Text type="secondary">
|
||||
已累计完成 {summary?.acceptedOrderCount || 0} 单,
|
||||
距下一等级还需 {Math.max(0, levelProgress.nextThreshold - (summary?.acceptedOrderCount || 0))} 单
|
||||
</Typography.Text>
|
||||
<Progress
|
||||
percent={levelProgress.progressPercent}
|
||||
size="small"
|
||||
format={(percent) => `${percent}%`}
|
||||
/>
|
||||
</Space>
|
||||
) : (
|
||||
<Typography.Text type="secondary">
|
||||
已累计完成 {summary?.acceptedOrderCount || 0} 单,当前已是最高等级
|
||||
</Typography.Text>
|
||||
)}
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
<Row gutter={[16, 16]}>
|
||||
@@ -542,6 +589,25 @@ export default function WorkerProfilePage() {
|
||||
<Descriptions.Item label="当前等级">
|
||||
{worker.level?.name || '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="我的邀请码">
|
||||
<Space size={4}>
|
||||
{worker.inviteCode || '-'}
|
||||
{worker.inviteCode ? (
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
icon={<CopyOutlined />}
|
||||
onClick={copyInviteCode}
|
||||
/>
|
||||
) : null}
|
||||
</Space>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="邀请人">
|
||||
{worker.inviter ? worker.inviter.displayName || worker.inviter.username : '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="已累计完成">
|
||||
{summary?.acceptedOrderCount || 0} 单
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="免押额度">
|
||||
{formatMoney(worker.level?.permissions.depositFreeAmount || 0)}
|
||||
</Descriptions.Item>
|
||||
|
||||
@@ -48,6 +48,7 @@ export function saveAdminWorkerLevel(payload: {
|
||||
name: string
|
||||
depositFreeAmount?: number
|
||||
maxActiveOrders?: number
|
||||
upgradeThreshold?: number
|
||||
}) {
|
||||
return apiPost<{ level: WorkerLevel }>(
|
||||
'/api/v1/admin/worker-platform/levels',
|
||||
|
||||
@@ -16,6 +16,7 @@ export function registerWorker(payload: {
|
||||
password: string
|
||||
displayName?: string
|
||||
phone?: string
|
||||
inviteCode?: string
|
||||
}) {
|
||||
return apiPost<{ worker: WorkerUser; reviewRequired: boolean }>(
|
||||
'/api/v1/worker/auth/register',
|
||||
|
||||
@@ -5,6 +5,12 @@ export type WorkerUser = {
|
||||
phone: string
|
||||
status: string
|
||||
reviewNote: string
|
||||
inviteCode: string
|
||||
inviter: null | {
|
||||
workerId: number
|
||||
username: string
|
||||
displayName: string
|
||||
}
|
||||
level: null | {
|
||||
levelId: number
|
||||
levelKey: string
|
||||
@@ -12,6 +18,7 @@ export type WorkerUser = {
|
||||
permissions: {
|
||||
depositFreeAmount: number
|
||||
maxActiveOrders: number
|
||||
upgradeThreshold: number
|
||||
}
|
||||
}
|
||||
wallet: {
|
||||
@@ -32,6 +39,12 @@ export type WorkerProfileSummary = {
|
||||
pendingRechargeAmount: number
|
||||
}
|
||||
|
||||
export type WorkerLevelProgress = {
|
||||
currentThreshold: number
|
||||
nextThreshold: number | null
|
||||
progressPercent: number
|
||||
}
|
||||
|
||||
export type WorkerWalletLedger = {
|
||||
ledgerId: number
|
||||
ledgerType: string
|
||||
@@ -86,6 +99,7 @@ export type WorkerProfileResponse = {
|
||||
worker: WorkerUser
|
||||
permissions: Record<string, unknown>
|
||||
summary: WorkerProfileSummary
|
||||
levelProgress: WorkerLevelProgress | null
|
||||
financeConfig: WorkerFinanceConfig
|
||||
}
|
||||
|
||||
@@ -97,6 +111,7 @@ export type WorkerLevel = {
|
||||
status: string
|
||||
depositFreeAmount: number
|
||||
maxActiveOrders: number
|
||||
upgradeThreshold: number
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user