修改河南项目的管理后台ui

This commit is contained in:
2026-09-27 19:12:59 +08:00
parent 34c7fa9d72
commit 3248ee4b83
31 changed files with 1605 additions and 558 deletions
+9 -1
View File
@@ -1,5 +1,5 @@
import request from '@/axios'
import type { RegisterUserType, SmsLoginType, UserType } from './types'
import type { DevLoginType, RegisterUserType, SmsLoginType, UserType } from './types'
interface RoleParams {
roleName: string
@@ -9,6 +9,14 @@ export const smsLoginApi = (data: SmsLoginType): Promise<IResponse<UserType>> =>
return request.post({ url: '/promo/smsLogin', data })
}
/**
* 开发环境专用的账号密码登录(仅 vite dev server 下可见)。
* 后端需在 sys_config 里把 promoDevLoginEnabled 置为 '1',否则返回「账号密码登录未开启」。
*/
export const devLoginApi = (data: DevLoginType): Promise<IResponse<UserType>> => {
return request.post({ url: '/promo/devLogin', data })
}
export const registerApi = (data: RegisterUserType): Promise<IResponse<UserType>> => {
return request.post({ url: '/promo/register', data })
}
+7
View File
@@ -10,6 +10,13 @@ export interface SmsLoginType {
parentInvitationCode?: string
}
/** 开发环境专用的账号密码登录入参(后端 promoDevLoginEnabled=1 才可用) */
export interface DevLoginType {
/** 推广账号(主播/招商/卡商)填手机号;超管可填登录账号或手机号 */
account: string
password: string
}
export interface UserType {
uid: number
username: string
@@ -5,14 +5,12 @@ import { UserInfo } from '@/components/UserInfo'
import { Breadcrumb } from '@/components/Breadcrumb'
import { useAppStore } from '@/store/modules/app'
import { useDesign } from '@/hooks/web/useDesign'
import { useUserStore } from '@/store/modules/user'
const { getPrefixCls, variables } = useDesign()
const prefixCls = getPrefixCls('tool-header')
const appStore = useAppStore()
const userStore = useUserStore()
// 面包屑
const breadcrumb = computed(() => appStore.getBreadcrumb)
@@ -42,9 +40,7 @@ export default defineComponent({
{breadcrumb.value ? <Breadcrumb class="<md:hidden"></Breadcrumb> : undefined}
</div>
) : undefined}
{userStore.getUserInfo?.role !== '99' && userStore.getUserInfo?.role !== '05' ? (
<span>分享链接:{userStore.getUserInfo?.shareLink}</span>
) : undefined}
{/* 顶栏的「分享链接」已移除:与首页「我的推广链接」hero 卡重复(2026-09-27) */}
<div class="h-full flex items-center">
<UserInfo></UserInfo>
</div>
+8 -2
View File
@@ -54,7 +54,7 @@ export const useAppStore = defineStore('app', {
tagsViewIcon: true, // 是否显示标签图标
logo: true, // logo
fixedHeader: true, // 固定toolheader
footer: true, // 显示页脚
footer: false, // 不显示页脚(推广后台不需要底部版权栏)
greyMode: false, // 是否开始灰色模式,用于特殊悼念日
dynamicRouter: true, // 是否动态路由
serverDynamicRouter: false, // 是否服务端渲染动态路由
@@ -336,7 +336,13 @@ export const useAppStore = defineStore('app', {
newTitle !== this.getTitle && this.setTitle(newTitle)
}
},
persist: true
persist: {
/* footer 不进持久化:本 store 原先 persist: true 会把 footer: true 写进 localStorage,
老用户下次进来 hydrate 又把它盖回 true ⇒ 光改上面的默认值页脚去不掉。
omit 在「存」和「取」两侧都生效(见 pinia-plugin-persistedstate dist/index.js:23),
所以旧数据里的 footer 会被直接忽略,永远取 state 默认值。 */
omit: ['footer']
}
})
export const useAppStoreWithOut = () => {
+28 -22
View File
@@ -5,6 +5,12 @@
<p class="desc">查看所有招商及其下属主播、普通用户的汇总数据</p>
</div>
<!--
三级表格的列一律用 min-width,不要改回 width:
Element Plus 只在存在弹性列时才把表格撑满容器,全列写死 width ⇒ 右侧留白。
(二级/三级表在展开行内,容器宽度由父级决定,所以更要用 min-width 跟随容器。)
详见 docs/修改记录.md「表格右侧留白」。
-->
<!-- 一级:招商列表 -->
<ElTable v-loading="loading" :data="pagedData" border stripe row-key="userId">
<ElTableColumn type="expand">
@@ -21,26 +27,26 @@
<!-- 三级:普通用户列表 -->
<ElTable :data="anchor.users || []" border size="small">
<ElTableColumn prop="userId" label="用户ID" width="90" />
<ElTableColumn prop="nickName" label="昵称" width="110" />
<ElTableColumn prop="userName" label="用户名" width="120" />
<ElTableColumn prop="createTime" label="创建时间" width="155" />
<ElTableColumn label="总充值" width="105">
<ElTableColumn prop="userId" label="用户ID" min-width="90" />
<ElTableColumn prop="nickName" label="昵称" min-width="110" />
<ElTableColumn prop="userName" label="用户名" min-width="120" />
<ElTableColumn prop="createTime" label="创建时间" min-width="155" />
<ElTableColumn label="总充值" min-width="105">
<template #default="{ row: u }">
{{ formatMoney(u.totalRecharge) }}
</template>
</ElTableColumn>
<ElTableColumn label="总流水" width="110">
<ElTableColumn label="总流水" min-width="110">
<template #default="{ row: u }">
{{ formatMoney(u.totalTurnover) }}
</template>
</ElTableColumn>
<ElTableColumn label="特殊游戏流水" width="115">
<ElTableColumn label="特殊游戏流水" min-width="115">
<template #default="{ row: u }">
{{ formatMoney(u.specialGameTurnover) }}
</template>
</ElTableColumn>
<ElTableColumn label="经典游戏流水" width="115">
<ElTableColumn label="经典游戏流水" min-width="115">
<template #default="{ row: u }">
{{ formatMoney(u.classicGameTurnover) }}
</template>
@@ -49,16 +55,16 @@
</div>
</template>
</ElTableColumn>
<ElTableColumn prop="userId" label="主播ID" width="120" />
<ElTableColumn prop="nickName" label="昵称" width="140" />
<ElTableColumn prop="userName" label="用户名" width="150" />
<ElTableColumn prop="userCount" label="下属用户数" width="110" />
<ElTableColumn label="主播充值" width="130">
<ElTableColumn prop="userId" label="主播ID" min-width="120" />
<ElTableColumn prop="nickName" label="昵称" min-width="140" />
<ElTableColumn prop="userName" label="用户名" min-width="150" />
<ElTableColumn prop="userCount" label="下属用户数" min-width="110" />
<ElTableColumn label="主播充值" min-width="130">
<template #default="{ row: a }">
{{ formatMoney(a.selfRecharge) }}
</template>
</ElTableColumn>
<ElTableColumn label="总收益" width="130">
<ElTableColumn label="总收益" min-width="130">
<template #default="{ row: a }">
{{ formatMoney(a.totalEarnings) }}
</template>
@@ -68,26 +74,26 @@
</template>
</ElTableColumn>
<ElTableColumn prop="userId" label="招商ID" width="120" />
<ElTableColumn prop="nickName" label="昵称" width="140" />
<ElTableColumn prop="userName" label="用户名" width="150" />
<ElTableColumn prop="anchorCount" label="下属主播数" width="110" />
<ElTableColumn label="总充值" width="130">
<ElTableColumn prop="userId" label="招商ID" min-width="120" />
<ElTableColumn prop="nickName" label="昵称" min-width="140" />
<ElTableColumn prop="userName" label="用户名" min-width="150" />
<ElTableColumn prop="anchorCount" label="下属主播数" min-width="110" />
<ElTableColumn label="总充值" min-width="130">
<template #default="{ row }">
{{ formatMoney(row.totalRecharge) }}
</template>
</ElTableColumn>
<ElTableColumn label="总流水" width="130">
<ElTableColumn label="总流水" min-width="130">
<template #default="{ row }">
{{ formatMoney(row.totalTurnover) }}
</template>
</ElTableColumn>
<ElTableColumn label="总收益" width="130">
<ElTableColumn label="总收益" min-width="130">
<template #default="{ row }">
{{ formatMoney(row.totalEarnings) }}
</template>
</ElTableColumn>
<ElTableColumn prop="createTime" label="创建时间" width="170" />
<ElTableColumn prop="createTime" label="创建时间" min-width="170" />
</ElTable>
<div class="pagination-wrap">
+13 -8
View File
@@ -36,6 +36,11 @@
<ElButton type="primary" @click="handleSearch">搜索</ElButton>
<ElButton @click="handleReset">重置</ElButton>
</div>
<!--
数据列一律用 min-width(「操作」列除外,它要固定宽):
Element Plus 只在存在弹性列时才把表格撑满容器,全列写死 width ⇒ 右侧留白。
详见 docs/修改记录.md「表格右侧留白」。
-->
<ElTable
:data="cardList"
row-key="cardCode"
@@ -44,8 +49,8 @@
v-loading="tableLoading"
style="width: 100%"
>
<ElTableColumn prop="id" label="ID" width="80" align="center" />
<ElTableColumn prop="cardCode" label="卡密" width="200" align="center">
<ElTableColumn prop="id" label="ID" min-width="80" align="center" />
<ElTableColumn prop="cardCode" label="卡密" min-width="200" align="center">
<template #default="{ row }">
<span class="card-code">{{ row.cardCode }}</span>
<ElButton type="primary" link size="small" @click="copyText(row.cardCode)">
@@ -53,26 +58,26 @@
</ElButton>
</template>
</ElTableColumn>
<ElTableColumn prop="amount" label="金额" width="100" align="center" />
<ElTableColumn prop="status" label="状态" width="90" align="center">
<ElTableColumn prop="amount" label="金额" min-width="100" align="center" />
<ElTableColumn prop="status" label="状态" min-width="90" align="center">
<template #default="{ row }">
<ElTag v-if="row.status === 0" type="success" size="small">未使用</ElTag>
<ElTag v-else-if="row.status === 2" type="danger" size="small">已销毁</ElTag>
<ElTag v-else type="info" size="small">已使用</ElTag>
</template>
</ElTableColumn>
<ElTableColumn prop="usedByName" label="使用者" width="140" align="center">
<ElTableColumn prop="usedByName" label="使用者" min-width="140" align="center">
<template #default="{ row }">
{{ row.usedByName || '-' }}
</template>
</ElTableColumn>
<ElTableColumn prop="usedTime" label="使用时间" width="180" align="center">
<ElTableColumn prop="usedTime" label="使用时间" min-width="180" align="center">
<template #default="{ row }">
{{ row.usedTime || '-' }}
</template>
</ElTableColumn>
<ElTableColumn prop="creatorName" label="创建者" width="140" align="center" />
<ElTableColumn prop="createTime" label="创建时间" width="180" align="center" />
<ElTableColumn prop="creatorName" label="创建者" min-width="140" align="center" />
<ElTableColumn prop="createTime" label="创建时间" min-width="180" align="center" />
<ElTableColumn label="操作" width="100" align="center" fixed="right">
<template #default="{ row }">
<ElPopconfirm
+24 -16
View File
@@ -1,5 +1,13 @@
<template>
<div class="page-container">
<!--
三张报表的列一律用 min-width,不要改回 width:
Element Plus 只在「存在弹性列」时才把表格撑满容器
(element-plus/es/components/table/src/table-layout.mjs 的 updateColumnsWidth)。
全列写死 width 时表格宽 = 各列宽之和 ⇒ 容器再宽右侧也会留白。
注意 setColumnWidth 会把 `width` 静态字符串 parseInt 成数字 ⇒ 那是固定列;
只写 `min-width` 才会把 column.width 置空 ⇒ 成为弹性列。详见 docs/修改记录.md。
-->
<ElTabs v-model="activeTab">
<ElTabPane label="佣金明细记录" name="detail">
<div class="search-bar">
@@ -10,21 +18,21 @@
<ElButton type="primary" :icon="Search" @click="loadDetail">查询</ElButton>
</div>
<ElTable v-loading="loading" :data="pagedDetailData" border stripe>
<ElTableColumn prop="localTime" label="本地时间" sortable width="170" />
<ElTableColumn prop="phone" label="手机号" width="130" />
<ElTableColumn prop="consumeAmount" sortable width="120">
<ElTableColumn prop="localTime" label="本地时间" sortable min-width="170" />
<ElTableColumn prop="phone" label="手机号" min-width="130" />
<ElTableColumn prop="consumeAmount" sortable min-width="120">
<template #header>
<span>该次消费/充值金额</span>
</template>
<template #default="{ row }"><span style="color:#f56c6c">{{ formatNum(row.consumeAmount) }}</span></template>
</ElTableColumn>
<ElTableColumn label="佣金比例" width="90">
<ElTableColumn label="佣金比例" min-width="90">
<template #default="{ row }">{{ formatRate(row.commissionRate, row.sourceGame) }}</template>
</ElTableColumn>
<ElTableColumn prop="commissionAmount" label="佣金金额" sortable width="100">
<ElTableColumn prop="commissionAmount" label="佣金金额" sortable min-width="100">
<template #default="{ row }"><span style="color:#67c23a">{{ formatNum(row.commissionAmount) }}</span></template>
</ElTableColumn>
<ElTableColumn prop="sourceGame" label="来源" width="120">
<ElTableColumn prop="sourceGame" label="来源" min-width="120">
<template #default="{ row }">{{ gameTypeLabel(row.sourceGame, row.originalSource) }}</template>
</ElTableColumn>
</ElTable>
@@ -42,17 +50,17 @@
<ElButton type="primary" :icon="Search" @click="loadDaily">查询</ElButton>
</div>
<ElTable v-loading="loading" :data="pagedDailyData" border stripe>
<ElTableColumn prop="phone" label="手机号" width="130" />
<ElTableColumn prop="consumeAmount" label="今日消费" sortable width="120">
<ElTableColumn prop="phone" label="手机号" min-width="130" />
<ElTableColumn prop="consumeAmount" label="今日消费" sortable min-width="120">
<template #default="{ row }"><span style="color:#f56c6c">{{ formatNum(row.consumeAmount) }}</span></template>
</ElTableColumn>
<ElTableColumn prop="gameType" label="来源" width="120">
<ElTableColumn prop="gameType" label="来源" min-width="120">
<template #default="{ row }">{{ gameTypeLabel(row.gameType) }}</template>
</ElTableColumn>
<ElTableColumn prop="commissionAmount" label="佣金金额" sortable width="120">
<ElTableColumn prop="commissionAmount" label="佣金金额" sortable min-width="120">
<template #default="{ row }"><span style="color:#67c23a">{{ formatNum(row.commissionAmount) }}</span></template>
</ElTableColumn>
<ElTableColumn prop="date" label="日期" sortable width="130" />
<ElTableColumn prop="date" label="日期" sortable min-width="130" />
</ElTable>
<div class="pagination-wrap">
<ElPagination v-model:current-page="dailyPage" v-model:page-size="dailySize" :page-sizes="[10,20,50]" :total="dailyData.length" layout="total, sizes, prev, pager, next, jumper" @size-change="dailyPage = 1" />
@@ -69,17 +77,17 @@
<ElButton type="primary" :icon="Search" @click="loadSummary">查询</ElButton>
</div>
<ElTable v-loading="loading" :data="pagedSummaryData" border stripe>
<ElTableColumn prop="phone" label="手机号" width="130" />
<ElTableColumn prop="gameType" label="来源" width="120">
<ElTableColumn prop="phone" label="手机号" min-width="130" />
<ElTableColumn prop="gameType" label="来源" min-width="120">
<template #default="{ row }">{{ gameTypeLabel(row.gameType) }}</template>
</ElTableColumn>
<ElTableColumn prop="totalFlow" label="消费金额" sortable width="120">
<ElTableColumn prop="totalFlow" label="消费金额" sortable min-width="120">
<template #default="{ row }"><span style="color:#f56c6c">{{ formatNum(row.totalFlow) }}</span></template>
</ElTableColumn>
<ElTableColumn prop="totalCommission" label="本次佣金" sortable width="120">
<ElTableColumn prop="totalCommission" label="本次佣金" sortable min-width="120">
<template #default="{ row }"><span style="color:#67c23a">{{ formatNum(row.totalCommission) }}</span></template>
</ElTableColumn>
<ElTableColumn prop="gameCount" label="参加游戏次数" sortable width="140" />
<ElTableColumn prop="gameCount" label="参加游戏次数" sortable min-width="140" />
</ElTable>
<div class="pagination-wrap">
<ElPagination v-model:current-page="summaryPage" v-model:page-size="summarySize" :page-sizes="[10,20,50]" :total="summaryData.length" layout="total, sizes, prev, pager, next, jumper" @size-change="summaryPage = 1" />
+379 -188
View File
@@ -1,8 +1,118 @@
<template>
<div class="home-page">
<!-- 基本信息 -->
<div class="section">
<h3 class="section-title">基本信息</h3>
<!-- ① 我的推广(置顶通栏):把每天真正要用的「链接 / 邀请码」提到最显眼处 -->
<section class="cc-card promo">
<div class="promo-hero">
<div class="hero-main">
<div class="hero-label">我的推广链接</div>
<div class="hero-link" :title="shareLink">
{{ shareLink || '暂未生成推广链接' }}
</div>
</div>
<ElButton
type="primary"
class="hero-btn"
:disabled="!shareLink"
@click="copyText(shareLink)"
>
<ElIcon :size="15"><DocumentCopy /></ElIcon>
<span class="hero-btn-text">复制链接</span>
</ElButton>
</div>
<div class="promo-cells">
<div class="pcell">
<div class="pcell-label">我的邀请码</div>
<div class="pcell-value">
<span class="pcell-strong">{{ userInfo.invitationCode || '-' }}</span>
<ElButton
type="primary"
link
size="small"
@click="copyText(userInfo.invitationCode)"
>
<ElIcon :size="14"><DocumentCopy /></ElIcon>
</ElButton>
<ElButton
v-if="!userInfo.invitationCodeModified"
type="warning"
link
size="small"
@click="editDialogVisible = true"
>
修改
</ElButton>
<span v-else class="pcell-tip">(已修改)</span>
</div>
</div>
<div class="pcell">
<div class="pcell-label">手机号码</div>
<div class="pcell-value">{{ userInfo.phoneNumber || '-' }}</div>
</div>
<div class="pcell">
<div class="pcell-label">上级邀请码</div>
<div class="pcell-value">{{ userInfo.parentInvitationCode || '-' }}</div>
</div>
</div>
</section>
<!-- ② 返利 KPI 横带:累计 / 今日 / 本周 / 下级 -->
<div class="kpi-band">
<div v-for="k in kpis" :key="k.label" class="kpi">
<div class="kpi-label">{{ k.label }}</div>
<div class="kpi-value" :class="k.tone">{{ k.value }}</div>
</div>
</div>
<!-- ③ 底部两栏:返利比例(左窄) | 周期返利汇总(右宽) -->
<div class="bottom-grid">
<section class="cc-card rate-panel">
<h3 class="cc-title">返利比例</h3>
<ul class="rate-list">
<li v-for="g in rateRows" :key="g.name" class="rate-row">
<span class="rate-name">{{ g.name }}</span>
<span class="rate-dots" aria-hidden="true"></span>
<span class="rate-val">{{ formatRate(g.rate) }}</span>
</li>
</ul>
</section>
<section class="cc-card table-panel">
<h3 class="cc-title">周期返利汇总</h3>
<ElTable :data="periodData" border stripe class="commission-table">
<ElTableColumn prop="period" label="周期" align="center" width="100" />
<ElTableColumn prop="classicBox" label="经典开箱" align="center" min-width="110">
<template #default="{ row }">{{ formatNum(row.classicBox) }}</template>
</ElTableColumn>
<ElTableColumn prop="luckyUpgrade" label="幸运升级" align="center" min-width="110">
<template #default="{ row }">{{ formatNum(row.luckyUpgrade) }}</template>
</ElTableColumn>
<ElTableColumn prop="battle" label="对战" align="center" min-width="110">
<template #default="{ row }">{{ formatNum(row.battle) }}</template>
</ElTableColumn>
<ElTableColumn prop="crazyWheel" label="疯狂转盘" align="center" min-width="110">
<template #default="{ row }">{{ formatNum(row.crazyWheel) }}</template>
</ElTableColumn>
<ElTableColumn prop="mahjong" label="麻将胡了" align="center" min-width="110">
<template #default="{ row }">{{ formatNum(row.mahjong) }}</template>
</ElTableColumn>
<ElTableColumn prop="totalConsume" label="总消费" align="center" min-width="110">
<template #default="{ row }">{{ formatNum(row.totalConsume) }}</template>
</ElTableColumn>
<ElTableColumn prop="recharge" label="充值" align="center" min-width="110">
<template #default="{ row }">{{ formatNum(row.recharge) }}</template>
</ElTableColumn>
<ElTableColumn prop="newSub" label="新增下级" align="center" min-width="100" />
<ElTableColumn prop="totalRebate" label="总返利" align="center" min-width="110">
<template #default="{ row }">{{ formatNum(row.totalRebate) }}</template>
</ElTableColumn>
</ElTable>
</section>
</div>
<!-- ④ 账号信息(降级到底部):日常用不到的身份/余额信息 -->
<section class="cc-card account-panel">
<h3 class="cc-title">账号信息</h3>
<ElDescriptions :column="3" border class="custom-desc">
<ElDescriptionsItem label="用户账号">
{{ userInfo.userId }}
@@ -16,121 +126,18 @@
<ElDescriptionsItem label="积分余额">
{{ userInfo.accountCredits ?? 0 }}
</ElDescriptionsItem>
<ElDescriptionsItem label="手机号码">
{{ userInfo.userName }}
</ElDescriptionsItem>
<ElDescriptionsItem label="注册时间">
{{ userInfo.createTime }}
</ElDescriptionsItem>
<ElDescriptionsItem label="Steam交易链接">
{{ userInfo.transactionLink || '-' }}
</ElDescriptionsItem>
<ElDescriptionsItem label="上级邀请码">
{{ userInfo.parentInvitationCode || '-' }}
</ElDescriptionsItem>
<ElDescriptionsItem label="我的邀请码">
{{ userInfo.invitationCode }}
<ElButton
type="primary"
link
size="small"
style="margin-left: 8px"
@click="copyText(userInfo.invitationCode)"
>
<ElIcon :size="14"><DocumentCopy /></ElIcon>
</ElButton>
<ElButton
v-if="!userInfo.invitationCodeModified"
type="warning"
link
size="small"
style="margin-left: 8px"
@click="editDialogVisible = true"
>
修改
</ElButton>
<span v-else style="margin-left: 8px; color: #909399; font-size: 12px">(已修改)</span>
</ElDescriptionsItem>
<ElDescriptionsItem label="推广链接">
{{ shareLink }}
<ElButton
v-if="shareLink"
type="primary"
link
size="small"
style="margin-left: 8px"
@click="copyText(shareLink)"
>
<ElIcon :size="14"><DocumentCopy /></ElIcon>
</ElButton>
</ElDescriptionsItem>
<ElDescriptionsItem label="" />
</ElDescriptions>
</div>
<!-- 游戏返利看板 -->
<div class="section">
<h3 class="section-title">游戏返利比例</h3>
<div class="game-rate-cards">
<div class="rate-card">
<div class="rate-card-title">经典开箱</div>
<div class="rate-card-value">{{ formatRate(gameRates.classicGameRate) }}</div>
</div>
<div class="rate-card">
<div class="rate-card-title">幸运升级</div>
<div class="rate-card-value">{{ formatRate(gameRates.classicGameRate) }}</div>
</div>
<div class="rate-card">
<div class="rate-card-title">对战</div>
<div class="rate-card-value">{{ formatRate(gameRates.classicGameRate) }}</div>
</div>
<div class="rate-card">
<div class="rate-card-title">疯狂转盘</div>
<div class="rate-card-value">{{ formatRate(gameRates.classicGameRate) }}</div>
</div>
<div class="rate-card">
<div class="rate-card-title">麻将胡了</div>
<div class="rate-card-value">{{ formatRate(gameRates.newGameRate) }}</div>
</div>
</div>
</div>
<!-- 周期返利汇总 -->
<div class="section">
<h3 class="section-title">周期返利汇总 </h3>
<ElTable :data="periodData" border stripe class="commission-table">
<ElTableColumn prop="period" label="周期" align="center" width="100" />
<ElTableColumn prop="classicBox" label="经典开箱" align="center" min-width="110">
<template #default="{ row }">{{ formatNum(row.classicBox) }}</template>
</ElTableColumn>
<ElTableColumn prop="luckyUpgrade" label="幸运升级" align="center" min-width="110">
<template #default="{ row }">{{ formatNum(row.luckyUpgrade) }}</template>
</ElTableColumn>
<ElTableColumn prop="battle" label="对战" align="center" min-width="110">
<template #default="{ row }">{{ formatNum(row.battle) }}</template>
</ElTableColumn>
<ElTableColumn prop="crazyWheel" label="疯狂转盘" align="center" min-width="110">
<template #default="{ row }">{{ formatNum(row.crazyWheel) }}</template>
</ElTableColumn>
<ElTableColumn prop="mahjong" label="麻将胡了" align="center" min-width="110">
<template #default="{ row }">{{ formatNum(row.mahjong) }}</template>
</ElTableColumn>
<ElTableColumn prop="totalConsume" label="总消费" align="center" min-width="110">
<template #default="{ row }">{{ formatNum(row.totalConsume) }}</template>
</ElTableColumn>
<ElTableColumn prop="recharge" label="充值" align="center" min-width="110">
<template #default="{ row }">{{ formatNum(row.recharge) }}</template>
</ElTableColumn>
<ElTableColumn prop="newSub" label="新增下级" align="center" min-width="100" />
<ElTableColumn prop="totalRebate" label="总返利" align="center" min-width="110">
<template #default="{ row }">{{ formatNum(row.totalRebate) }}</template>
</ElTableColumn>
</ElTable>
</div>
</section>
<!-- 修改邀请码弹窗 -->
<ElDialog v-model="editDialogVisible" title="修改邀请码" width="400px">
<p style="color: #e6a23c; margin-bottom: 12px">注意:邀请码只能修改一次,请谨慎操作!</p>
<p style="color: #8f6418; margin-bottom: 12px">注意:邀请码只能修改一次,请谨慎操作!</p>
<ElForm>
<ElFormItem label="新邀请码">
<ElInput v-model="newInvitationCode" placeholder="请输入 6 位邀请码" maxlength="6" :pattern="/^[a-zA-Z0-9]*$/" :rules="[{ required: true, message: '邀请码只能包含英文字母和数字', trigger: 'blur' }]" />
@@ -150,12 +157,10 @@ import { ElDescriptions, ElDescriptionsItem, ElButton, ElMessage, ElDialog, ElFo
import { DocumentCopy } from '@element-plus/icons-vue'
import { getInfo, getShareDomain, updateInvitationCode } from '@/api/user'
import { getGameCommissionData } from '@/api/turnover'
import { useUserStore } from '@/store/modules/user'
// 用户基本信息
const userInfo = reactive<any>({})
const shareLink = ref('')
const userStore = useUserStore()
// 修改邀请码弹窗
const editDialogVisible = ref(false)
@@ -183,13 +188,6 @@ const submitInvitationCode = async () => {
editDialogVisible.value = false
newInvitationCode.value = ''
await loadUserInfo()
// 同步更新顶部 header 中的分享链接
if (userStore.getUserInfo) {
userStore.setUserInfo({
...userStore.getUserInfo,
shareLink: shareLink.value
})
}
} else {
ElMessage.error(res.msg || '修改失败')
}
@@ -205,7 +203,7 @@ const gameRates = reactive({
classicGameRate: 0
})
// 周期返利汇总数据
// 周期返利汇总数据(后端顺序固定:今日 / 昨日 / 本周 / 总)
const periodData = ref([] as any[])
// 用户类型映射
@@ -221,6 +219,38 @@ const userTypeLabel = computed(() => {
return map[userInfo.userType] || '未知'
})
/**
* 返利比例列表。后端只下发三个比例(充值 / 新游戏 / 经典玩法),
* 5 个玩法里「麻将胡了」走新游戏比例,其余 4 个经典玩法共用经典比例
* —— 与后端 buildPeriod 里 classicConsume / mahjong 的算法口径一致。
*/
const rateRows = computed(() => [
{ name: '经典开箱', rate: gameRates.classicGameRate },
{ name: '幸运升级', rate: gameRates.classicGameRate },
{ name: '对战', rate: gameRates.classicGameRate },
{ name: '疯狂转盘', rate: gameRates.classicGameRate },
{ name: '麻将胡了', rate: gameRates.newGameRate }
])
// 按周期名取 periodData 里的一行
const periodOf = (name: string) => periodData.value.find((p: any) => p.period === name)
/**
* 顶部 KPI 横带:累计返利 / 今日返利 / 本周返利 / 累计下级。
* 全部取自周期返利汇总的「总 / 今日 / 本周」行,不额外发请求。
*/
const kpis = computed(() => {
const total = periodOf('总')
const today = periodOf('今日')
const week = periodOf('本周')
return [
{ label: '累计返利', value: formatNum(total?.totalRebate ?? 0), tone: 'is-primary' },
{ label: '今日返利', value: formatNum(today?.totalRebate ?? 0), tone: '' },
{ label: '本周返利', value: formatNum(week?.totalRebate ?? 0), tone: '' },
{ label: '累计下级', value: String(total?.newSub ?? 0), tone: 'is-success' }
]
})
// 复制到剪贴板(兼容非 HTTPS 环境)
const copyText = (text: string) => {
if (navigator.clipboard) {
@@ -322,97 +352,258 @@ onMounted(async () => {
<style scoped>
.home-page {
padding: 5px 10px;
}
.section {
margin-bottom: 30px;
}
.section-title {
font-size: 16px;
font-weight: bold;
color: #303133;
margin: 0 0 15px 0;
}
.user-type-anchor {
color: #e6a23c;
}
/* 覆盖 ElDescriptions 默认的 label 灰色背景 */
.custom-desc :deep(.el-descriptions__label) {
background-color: #fff;
color: #606266;
font-weight: normal;
width: 120px;
}
.custom-desc :deep(.el-descriptions__content) {
background-color: #fff;
}
/* 游戏返利比例卡片 */
.game-rate-cards {
display: flex;
gap: 15px;
justify-content: space-between;
flex-wrap: wrap;
flex-direction: column;
gap: 16px;
}
.rate-card {
flex: 1;
min-width: 150px;
max-width: calc(20% - 12px);
background-color: #f5f7fa;
border: 1px solid #e4e7ed;
border-radius: 6px;
padding: 20px 15px;
text-align: center;
transition: all 0.3s ease;
/* 通用卡片外壳(田园核:奶油面 + 亚麻描边) */
.cc-card {
background-color: #fffdfa;
border: 1px solid #eadfd0;
border-radius: 12px;
padding: 18px 20px;
box-shadow: 0 2px 10px rgba(139, 115, 85, 0.06);
}
.rate-card:hover {
background-color: #fbf5ea;
border-color: #9a5c35;
transform: translateY(-2px);
box-shadow: 0 2px 12px rgba(154, 92, 53, 0.10);
}
.rate-card-title {
.cc-title {
font-size: 15px;
font-weight: bold;
color: #303133;
font-weight: 700;
color: #4f4130;
margin: 0 0 14px 0;
}
/* ===== ① 我的推广 ===== */
.promo-hero {
display: flex;
align-items: center;
gap: 16px;
padding: 14px 16px;
background-color: #fbf5ea;
border: 1px solid #eadfd0;
border-radius: 10px;
}
.hero-main {
flex: 1;
min-width: 0;
}
.hero-label {
font-size: 12px;
color: #7a664e;
margin-bottom: 6px;
}
.hero-link {
font-size: 15px;
font-weight: 600;
color: #4f4130;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-variant-numeric: tabular-nums;
}
.hero-btn {
flex: none;
}
.hero-btn-text {
margin-left: 6px;
}
.promo-cells {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
margin-top: 16px;
padding-top: 14px;
border-top: 1px dashed #eadfd0;
}
.pcell {
padding: 0 18px;
border-left: 1px solid #eadfd0;
min-width: 0;
}
.pcell:first-child {
padding-left: 0;
border-left: none;
}
.pcell-label {
font-size: 12px;
color: #7a664e;
margin-bottom: 6px;
}
.pcell-value {
display: flex;
align-items: center;
font-size: 15px;
color: #4f4130;
min-width: 0;
}
.pcell-strong {
font-weight: 700;
color: #9a5c35;
letter-spacing: 1px;
}
.pcell-tip {
margin-left: 8px;
font-size: 12px;
color: #7a664e;
}
/* ===== ② KPI 横带 ===== */
.kpi-band {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
background-color: #fffdfa;
border: 1px solid #eadfd0;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 10px rgba(139, 115, 85, 0.06);
}
.kpi {
padding: 16px 20px;
border-left: 1px solid #eadfd0;
}
.kpi:first-child {
border-left: none;
}
.kpi-label {
font-size: 12px;
color: #7a664e;
margin-bottom: 8px;
}
.rate-card-sub {
font-size: 12px;
color: #909399;
margin-bottom: 12px;
.kpi-value {
font-size: 19px;
font-weight: 700;
line-height: 1.2;
color: #4f4130;
font-variant-numeric: tabular-nums;
}
.rate-card-value {
font-size: 20px;
font-weight: bold;
color: #67c23a;
.kpi-value.is-primary {
color: #9a5c35;
}
.kpi-value.is-success {
color: #3f6b45;
}
/* ===== ③ 底部两栏 ===== */
.bottom-grid {
display: grid;
grid-template-columns: 260px minmax(0, 1fr);
gap: 16px;
align-items: start;
}
.rate-list {
list-style: none;
margin: 0;
padding: 0;
}
.rate-row {
display: flex;
align-items: baseline;
gap: 8px;
padding: 10px 0;
border-bottom: 1px dashed #eadfd0;
}
.rate-row:last-child {
border-bottom: none;
}
.rate-name {
font-size: 14px;
color: #5f4f3d;
flex: none;
}
.rate-dots {
flex: 1;
border-bottom: 1px dotted #d8c3b0;
transform: translateY(-3px);
}
.rate-val {
flex: none;
font-size: 15px;
font-weight: 700;
color: #3f6b45;
font-variant-numeric: tabular-nums;
}
.table-panel {
min-width: 0;
overflow: hidden;
}
/* 返利汇总表格 */
.commission-table {
width: 100%;
}
.commission-table :deep(.el-table__header th) {
background-color: #fafafa !important;
color: #606266;
font-weight: bold;
background-color: #f6efe3 !important;
color: #5f4f3d;
font-weight: 700;
}
.commission-table :deep(.el-table__row tr:nth-child(odd)) {
background-color: #fafafa;
/* ===== ④ 账号信息(降级:不做成卡片,别和上面的数据抢注意力) ===== */
.account-panel {
background-color: transparent;
border-color: transparent;
box-shadow: none;
}
.commission-table :deep(.el-table__row tr:nth-child(even)) {
background-color: #fff;
.user-type-anchor {
color: #8f6418;
}
.custom-desc :deep(.el-descriptions__label) {
background-color: #f6efe3;
color: #7a664e;
font-weight: normal;
width: 110px;
}
.custom-desc :deep(.el-descriptions__content) {
background-color: #fffdfa;
color: #4f4130;
}
/* 窄屏:底部两栏收成一栏,表格拿满宽度 */
@media (max-width: 1200px) {
.bottom-grid {
grid-template-columns: minmax(0, 1fr);
}
}
@media (max-width: 900px) {
.kpi-band {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.promo-cells {
grid-template-columns: minmax(0, 1fr);
gap: 14px;
}
.pcell {
padding: 0;
border-left: none;
}
}
</style>
+7 -78
View File
@@ -215,86 +215,15 @@ const inviteCode = computed(() => {
/* =========================
验证码行
只改按钮,不碰 input / row / col 的布局
「获取验证码」按钮的配色写在 LoginForm.vue 里(.login-code-btn)。
这里原本有一条 `.el-form-item .el-row .el-col:last-child .el-button`
和一组 .send-code / .code-button / .get-code-btn,都是死代码:
Form.vue 的实际结构是 ElRow > ElCol > ElFormItem > 插槽内容,
el-row 是 el-form-item 的祖先而不是后代,选择器永远匹配不到;
类名也对不上(真实类名是 .login-code-btn)。
不要再往这里加按钮样式,改 LoginForm.vue。
========================= */
/*
LoginForm 常见结构:
el-form-item
el-row
el-col -> input
el-col -> button
这里仅让右侧按钮与输入框同高、同圆角、同玻璃质感。
*/
:deep(.el-form-item .el-row .el-col:last-child .el-button) {
width: 100%;
height: 48px;
padding: 0 16px;
border-radius: 12px;
border: 1px solid rgba(212, 160, 160, 0.6);
background: rgba(245, 215, 95, 0.22);
color: #9a5c35;
font-size: 13px;
font-weight: 600;
box-shadow: none;
transition:
background 0.2s ease,
border-color 0.2s ease,
color 0.2s ease,
transform 0.2s ease;
&:hover {
color: #7d4a2a;
border-color: #c08a8a;
background: rgba(245, 215, 95, 0.34);
}
&:active {
transform: scale(0.97);
}
&.is-disabled,
&:disabled {
color: #9a886e;
border-color: #eadfd0;
background: #f6efe3;
}
}
/*
如果验证码按钮不是放在 el-row/el-col 里,
兼容常见的 send-code / code-button 类。
*/
:deep(.send-code),
:deep(.code-button),
:deep(.get-code-btn) {
height: 48px;
border-radius: 12px;
border: 1px solid rgba(212, 160, 160, 0.6);
background: rgba(245, 215, 95, 0.22);
color: #9a5c35;
font-size: 13px;
font-weight: 600;
}
/* =========================
登录主按钮
========================= */
@@ -1,6 +1,12 @@
<script setup lang="tsx">
import { getAdminRoleApi, getCodeApi, precheckPhoneApi, smsLoginApi } from '@/api/login'
import type { SmsLoginType, UserType } from '@/api/login/types'
import {
devLoginApi,
getAdminRoleApi,
getCodeApi,
precheckPhoneApi,
smsLoginApi
} from '@/api/login'
import type { DevLoginType, SmsLoginType, UserType } from '@/api/login/types'
import { getMyMenus } from '@/api/permission'
import { PATH_URL } from '@/axios/service'
import { BaseButton } from '@/components/Button'
@@ -25,12 +31,20 @@ const { currentRoute, addRoute, push, resolve } = router
const { required } = useValidator()
const { formRegister, formMethods } = useForm()
const { getFormData, getElFormExpose, setValues } = formMethods
// 账号密码登录用独立表单实例:与短信表单互斥渲染(v-if 切换),各自持有自己的 el-form
const { formRegister: pwdFormRegister, formMethods: pwdFormMethods } = useForm()
const { getFormData: getPwdFormData, getElFormExpose: getPwdElFormExpose } = pwdFormMethods
// 招商邀请链接里带的邀请码(/login?code=xxx),进入后自动填到邀请码框
const props = defineProps<{
inviteCode?: string
}>()
// 账号密码登录只在本地 dev server(pnpm run dev)下提供:
// 生产/测试用 vite build 打包时 import.meta.env.DEV 为 false,入口与表单整块不渲染
const devPasswordLogin = import.meta.env.DEV
const loginMode = ref<'sms' | 'password'>('sms')
const loading = ref(false)
const redirect = ref('')
const getCodeTime = ref(60)
@@ -62,19 +76,27 @@ const rules = {
code: [required()]
}
const passwordRules = {
account: [required()],
password: [required()]
}
// 两种登录模式共用的标题,保证卡片标题样式完全一致
const renderLoginTitle = () => (
<div class="login-form-title">
<div class="title-decoration"></div>
<h2>登录</h2>
<div class="title-decoration"></div>
</div>
)
const schema = reactive<FormSchema[]>([
{
field: 'title',
colProps: { span: 24 },
formItemProps: {
slots: {
default: () => (
<div class="login-form-title">
<div class="title-decoration"></div>
<h2>登录</h2>
<div class="title-decoration"></div>
</div>
)
default: renderLoginTitle
}
}
},
@@ -162,6 +184,82 @@ const schema = reactive<FormSchema[]>([
}
])
// 账号密码登录表单:字段口径与后端 /promo/devLogin 一致(推广账号填手机号,超管可填账号名或手机号)
const passwordSchema = reactive<FormSchema[]>([
{
field: 'title',
colProps: { span: 24 },
formItemProps: {
slots: {
default: renderLoginTitle
}
}
},
{
field: 'account',
label: '',
value: '',
colProps: { span: 24 },
formItemProps: {
slots: {
default: (formData) => (
<ElInput
v-model={formData.account}
maxlength={32}
clearable
placeholder="请输入账号(推广账号填手机号)"
onKeydown={(event: KeyboardEvent) => {
if (event.key === 'Enter') passwordSignIn()
}}
/>
)
}
}
},
{
field: 'password',
label: '',
value: '',
colProps: { span: 24 },
formItemProps: {
slots: {
default: (formData) => (
<ElInput
v-model={formData.password}
type="password"
showPassword
maxlength={32}
placeholder="请输入密码"
onKeydown={(event: KeyboardEvent) => {
if (event.key === 'Enter') passwordSignIn()
}}
/>
)
}
}
},
{
field: 'login',
colProps: { span: 24 },
formItemProps: {
slots: {
default: () => (
<div class="login-form-btn w-[100%]">
<BaseButton
loading={loading.value}
type="primary"
class="full-width"
onClick={passwordSignIn}
>
登录
</BaseButton>
</div>
)
}
}
}
])
watch(
() => currentRoute.value,
(route: RouteLocationNormalizedLoaded) => {
@@ -287,6 +385,37 @@ const signIn = async () => {
}
}
// 账号密码登录(仅 dev server 可见):后端 /promo/devLogin 校验 BCrypt 密码后直接签发 token,
// 之后的落地流程与短信登录完全一致
const passwordSignIn = async () => {
if (loading.value) return
const formRef = await getPwdElFormExpose()
if (
!(await formRef
?.validateField(['account', 'password'])
.then(() => true)
.catch(() => false))
)
return
loading.value = true
try {
const formData = await getPwdFormData<DevLoginType>()
const res = await devLoginApi({
account: formData.account,
password: formData.password
})
if (!res) return
userStore.setLoginInfo(undefined)
userStore.setRememberMe(false)
await completeLogin(formData.account, (res as any).token)
await enterSystem(formData.account)
} finally {
loading.value = false
}
}
const getRoleRouters = async () => {
const res = (await getMyMenus()) as any
return { code: 0, data: res.data || [] }
@@ -329,18 +458,96 @@ const enterSystem = async (username: string) => {
</script>
<template>
<!-- 登录方式切换:仅本地 dev server 渲染,生产/测试构建包不会出现该入口 -->
<div v-if="devPasswordLogin" class="login-mode-switch">
<button
type="button"
class="login-mode-item"
:class="{ 'is-active': loginMode === 'sms' }"
@click="loginMode = 'sms'"
>
短信验证码登录
</button>
<button
type="button"
class="login-mode-item"
:class="{ 'is-active': loginMode === 'password' }"
@click="loginMode = 'password'"
>
账号密码登录
</button>
</div>
<Form
:schema="schema"
:rules="rules"
v-if="devPasswordLogin && loginMode === 'password'"
:schema="passwordSchema"
:rules="passwordRules"
label-position="top"
hide-required-asterisk
size="large"
@register="formRegister"
@register="pwdFormRegister"
/>
<div id="captcha-box" class="captcha-box"></div>
<template v-else>
<Form
:schema="schema"
:rules="rules"
label-position="top"
hide-required-asterisk
size="large"
@register="formRegister"
/>
<div id="captcha-box" class="captcha-box"></div>
</template>
</template>
<style lang="less" scoped>
/* =========================
登录方式切换(仅 dev server 渲染)
与卡片同色系:默认低调暖棕文字,选中态用奶油底 + 花粉细边
对比度:默认 5.9:1 / hover 5.3:1 / 选中 6.3:1,均过 WCAG AA
========================= */
.login-mode-switch {
display: flex;
justify-content: center;
gap: 10px;
margin-bottom: 20px;
}
.login-mode-item {
padding: 7px 16px;
font-family: inherit;
font-size: 13px;
font-weight: 500;
color: #7a664e;
cursor: pointer;
background: transparent;
border: 1px solid transparent;
border-radius: 999px;
transition:
color 0.2s ease,
background 0.2s ease,
border-color 0.2s ease;
&:hover {
color: #9a5c35;
border-color: rgba(212, 160, 160, 0.45);
}
&.is-active {
color: #854c2a;
font-weight: 600;
background: #fbf5ea;
border-color: rgba(212, 160, 160, 0.55);
}
}
.captcha-box {
position: fixed;
top: 50%;
@@ -361,32 +568,39 @@ const enterSystem = async (username: string) => {
min-width: 0;
}
/* 独立圆角按钮:拼接式(el-input-group__append)左侧必须是直角,四角圆角做不到 */
/* 独立圆角按钮:拼接式(el-input-group__append)左侧必须是直角,四角圆角做不到。
原紫蓝半透明底(rgba(126,132,240,.3))+ 近白字(#eef0ff)是给深色背景配的,
搬到奶油卡片上后字面比底色还亮(对比度约 0.8:1),按钮看上去就是一块空白;
改用主题色板内色值:默认 6.31:1 / hover 5.99:1 / 倒计时 5.00:1,均过 WCAG AA */
:deep(.sms-code-row .login-code-btn) {
flex: 0 0 124px;
height: 48px;
margin: 0 0 0 10px;
padding: 0;
color: #eef0ff;
color: #854c2a;
font-size: 13px;
font-weight: 600;
border: 1px solid rgba(150, 158, 255, 0.42);
border: 1px solid rgba(212, 160, 160, 0.55);
border-radius: 12px;
background: rgba(126, 132, 240, 0.3);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14);
background: #fbf5ea;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.6);
transition:
background 0.2s ease,
border-color 0.2s ease,
color 0.2s ease;
}
:deep(.sms-code-row .login-code-btn:hover) {
color: #fff;
border-color: rgba(172, 180, 255, 0.62);
background: rgba(126, 132, 240, 0.44);
color: #854c2a;
border-color: #c08a8a;
background: #f6efe3;
}
:deep(.sms-code-row .login-code-btn.is-disabled),
:deep(.sms-code-row .login-code-btn.is-disabled:hover) {
color: rgba(230, 232, 255, 0.45);
border-color: rgba(255, 255, 255, 0.12);
background: rgba(126, 132, 240, 0.14);
color: #6b6152;
border-color: #eadfd0;
background: #f0e8dc;
}
:deep(.login-form-btn .el-button) {
@@ -19,21 +19,22 @@
<ElButton type="primary" :icon="Search" @click="loadList">查询</ElButton>
</div>
<!-- 列宽一律用 min-width:全列写死 width 时 Element Plus 不拉伸,表格右侧会留白(见 docs/修改记录.md) -->
<ElTable v-loading="loading" :data="pagedData" border stripe>
<ElTableColumn prop="rechargeTime" label="充值时间" sortable width="170" />
<ElTableColumn prop="userId" label="用户ID" width="120" />
<ElTableColumn prop="parentId" label="上级ID" width="120">
<ElTableColumn prop="rechargeTime" label="充值时间" sortable min-width="170" />
<ElTableColumn prop="userId" label="用户ID" min-width="120" />
<ElTableColumn prop="parentId" label="上级ID" min-width="120">
<template #default="{ row }">
{{ row.parentId || '-' }}
</template>
</ElTableColumn>
<ElTableColumn prop="phone" label="手机号" width="130" />
<ElTableColumn prop="userType" label="用户类型" width="100">
<ElTableColumn prop="phone" label="手机号" min-width="130" />
<ElTableColumn prop="userType" label="用户类型" min-width="100">
<template #default="{ row }">
{{ row.userType === '01' ? '主播' : row.userType === '02' ? '玩家' : row.userType === '04' ? '招商' : row.userType || '-' }}
</template>
</ElTableColumn>
<ElTableColumn prop="rechargeAmount" label="充值金额" sortable width="120">
<ElTableColumn prop="rechargeAmount" label="充值金额" sortable min-width="120">
<template #default="{ row }">
<span style="color: #67c23a">{{ formatNum(row.rechargeAmount) }}</span>
</template>
+17 -13
View File
@@ -13,6 +13,10 @@
>
</el-form-item>
</el-form>
<!--
数据列一律用 min-width(「操作」列除外,它要固定宽):
全列写死 width 时 Element Plus 不拉伸表格 ⇒ 右侧留白(见 docs/修改记录.md)。
-->
<el-table
:data="alltableData"
style="width: 100%; margin-bottom: 20px; margin-top: 10px"
@@ -21,25 +25,25 @@
default-expand-all
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
>
<el-table-column prop="userId" label="用户ID" width="120" />
<el-table-column prop="userName" label="用户名" width="140" />
<el-table-column prop="nickName" label="昵称" width="120" />
<el-table-column prop="userType" label="用户类型" width="100">
<el-table-column prop="userId" label="用户ID" min-width="120" />
<el-table-column prop="userName" label="用户名" min-width="140" />
<el-table-column prop="nickName" label="昵称" min-width="120" />
<el-table-column prop="userType" label="用户类型" min-width="100">
<template #default="scope">
<el-tag v-if="scope.row.userType === '01'" type="warning" size="small">主播</el-tag>
<el-tag v-else-if="scope.row.userType === '02'" type="info" size="small">玩家</el-tag>
<el-tag v-else size="small">未知</el-tag>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="170" />
<el-table-column prop="totalRecharge" label="总充值" width="100" />
<el-table-column prop="classicBoxConsume" label="经典开箱" width="100" />
<el-table-column prop="battleConsume" label="对战" width="100" />
<el-table-column prop="luckUpgradeConsume" label="幸运升级" width="100" />
<el-table-column prop="crazyWheelConsume" label="疯狂转盘" width="100" />
<el-table-column prop="westDuelConsume" label="荒野争锋" width="100" />
<el-table-column prop="sweetConsume" label="极速糖果" width="100" />
<el-table-column prop="totalCommission" label="总佣金" width="100" />
<el-table-column prop="createTime" label="创建时间" min-width="170" />
<el-table-column prop="totalRecharge" label="总充值" min-width="100" />
<el-table-column prop="classicBoxConsume" label="经典开箱" min-width="100" />
<el-table-column prop="battleConsume" label="对战" min-width="100" />
<el-table-column prop="luckUpgradeConsume" label="幸运升级" min-width="100" />
<el-table-column prop="crazyWheelConsume" label="疯狂转盘" min-width="100" />
<el-table-column prop="westDuelConsume" label="荒野争锋" min-width="100" />
<el-table-column prop="sweetConsume" label="极速糖果" min-width="100" />
<el-table-column prop="totalCommission" label="总佣金" min-width="100" />
<el-table-column label="操作" align="center" width="120" class-name="small-padding fixed-width">
<template #default="scope">
<el-button