修改河南项目的管理后台ui
This commit is contained in:
@@ -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 })
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user