逻辑优化
This commit is contained in:
parent
e3d91cc13b
commit
cb14edc316
|
|
@ -20,6 +20,11 @@ public class OrderDto
|
|||
/// </summary>
|
||||
public long UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户UID
|
||||
/// </summary>
|
||||
public string? UserUid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户昵称
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,11 @@ public class OrderQueryRequest : PagedRequest
|
|||
/// </summary>
|
||||
public long? UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户UID(模糊搜索)
|
||||
/// </summary>
|
||||
public string? UserUid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 订单类型:1测评订单 2预约订单
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,11 @@ namespace MiAssessment.Admin.Business.Models.User;
|
|||
/// </summary>
|
||||
public class UserQueryRequest : PagedRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 上级用户ID(查询某用户的下级列表)
|
||||
/// </summary>
|
||||
public long? ParentUserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户UID
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ public class OrderService : IOrderService
|
|||
Id = o.Id,
|
||||
OrderNo = o.OrderNo,
|
||||
UserId = o.UserId,
|
||||
UserUid = o.User != null ? o.User.Uid : null,
|
||||
UserNickname = o.User != null ? o.User.Nickname : null,
|
||||
UserPhone = o.User != null ? o.User.Phone : null,
|
||||
OrderType = o.OrderType,
|
||||
|
|
@ -137,6 +138,7 @@ public class OrderService : IOrderService
|
|||
Id = order.Id,
|
||||
OrderNo = order.OrderNo,
|
||||
UserId = order.UserId,
|
||||
UserUid = order.User?.Uid,
|
||||
UserNickname = order.User?.Nickname,
|
||||
UserPhone = order.User?.Phone,
|
||||
OrderType = order.OrderType,
|
||||
|
|
@ -282,6 +284,7 @@ public class OrderService : IOrderService
|
|||
Id = o.Id,
|
||||
OrderNo = o.OrderNo,
|
||||
UserId = o.UserId,
|
||||
UserUid = o.User != null ? o.User.Uid : null,
|
||||
UserNickname = o.User != null ? o.User.Nickname : null,
|
||||
UserPhone = o.User != null ? o.User.Phone : null,
|
||||
OrderType = o.OrderType,
|
||||
|
|
@ -343,6 +346,12 @@ public class OrderService : IOrderService
|
|||
query = query.Where(o => o.UserId == request.UserId.Value);
|
||||
}
|
||||
|
||||
// 按用户UID搜索
|
||||
if (!string.IsNullOrWhiteSpace(request.UserUid))
|
||||
{
|
||||
query = query.Where(o => o.User != null && o.User.Uid.Contains(request.UserUid));
|
||||
}
|
||||
|
||||
// 按订单类型筛选
|
||||
if (request.OrderType.HasValue)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -380,6 +380,12 @@ public class UserBusinessService : IUserBusinessService
|
|||
/// <returns>过滤后的查询</returns>
|
||||
private IQueryable<User> ApplyUserQueryFilters(IQueryable<User> query, UserQueryRequest request)
|
||||
{
|
||||
// 按上级用户ID筛选(查询下级用户)
|
||||
if (request.ParentUserId.HasValue)
|
||||
{
|
||||
query = query.Where(u => u.ParentUserId == request.ParentUserId.Value);
|
||||
}
|
||||
|
||||
// 按UID筛选
|
||||
if (!string.IsNullOrWhiteSpace(request.Uid))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ export interface OrderItem {
|
|||
orderNo: string
|
||||
/** 用户ID */
|
||||
userId: number
|
||||
/** 用户UID */
|
||||
userUid: string
|
||||
/** 用户昵称 */
|
||||
userNickname: string
|
||||
/** 用户手机号 */
|
||||
|
|
@ -79,6 +81,8 @@ export interface OrderQuery extends PagedRequest {
|
|||
orderNo?: string
|
||||
/** 用户ID */
|
||||
userId?: number
|
||||
/** 用户UID(模糊搜索) */
|
||||
userUid?: string
|
||||
/** 订单类型筛选 */
|
||||
orderType?: number
|
||||
/** 订单状态筛选 */
|
||||
|
|
|
|||
|
|
@ -65,6 +65,8 @@ export interface UserDetail extends UserItem {
|
|||
* 用户查询参数
|
||||
*/
|
||||
export interface UserQuery extends PagedRequest {
|
||||
/** 上级用户ID(查询下级列表) */
|
||||
parentUserId?: number
|
||||
/** 用户UID(模糊搜索) */
|
||||
uid?: string
|
||||
/** 手机号(模糊搜索) */
|
||||
|
|
|
|||
|
|
@ -27,10 +27,10 @@
|
|||
@keyup.enter="handleSearch"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户ID">
|
||||
<el-form-item label="用户UID">
|
||||
<el-input
|
||||
v-model="queryParams.userId"
|
||||
placeholder="请输入用户ID"
|
||||
v-model="queryParams.userUid"
|
||||
placeholder="请输入用户UID"
|
||||
clearable
|
||||
@keyup.enter="handleSearch"
|
||||
/>
|
||||
|
|
@ -96,10 +96,11 @@
|
|||
<el-table-column prop="orderNo" label="订单号" width="180" show-overflow-tooltip />
|
||||
|
||||
<!-- 用户信息 -->
|
||||
<el-table-column label="用户信息" min-width="150">
|
||||
<el-table-column label="用户信息" min-width="180">
|
||||
<template #default="{ row }">
|
||||
<div class="user-info">
|
||||
<div class="nickname">{{ row.userNickname }}</div>
|
||||
<div class="uid">UID: {{ row.userUid }}</div>
|
||||
<div class="phone">{{ row.userPhone }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -216,7 +217,7 @@
|
|||
<div class="detail-section">
|
||||
<h4 class="section-title">用户信息</h4>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="用户ID">{{ state.orderDetail.userId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="用户UID">{{ state.orderDetail.userUid }}</el-descriptions-item>
|
||||
<el-descriptions-item label="用户昵称">{{ state.orderDetail.userNickname }}</el-descriptions-item>
|
||||
<el-descriptions-item label="手机号" :span="2">{{ state.orderDetail.userPhone }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
|
@ -448,7 +449,7 @@ const queryParams = reactive({
|
|||
page: 1,
|
||||
pageSize: 10,
|
||||
orderNo: '',
|
||||
userId: '',
|
||||
userUid: '',
|
||||
orderType: undefined as string | undefined,
|
||||
status: undefined as string | undefined,
|
||||
payType: undefined as string | undefined,
|
||||
|
|
@ -631,8 +632,8 @@ async function loadOrderList() {
|
|||
if (queryParams.orderNo) {
|
||||
params.orderNo = queryParams.orderNo
|
||||
}
|
||||
if (queryParams.userId) {
|
||||
params.userId = Number(queryParams.userId)
|
||||
if (queryParams.userUid) {
|
||||
params.userUid = queryParams.userUid
|
||||
}
|
||||
if (queryParams.orderType !== undefined && queryParams.orderType !== '') {
|
||||
params.orderType = Number(queryParams.orderType)
|
||||
|
|
@ -701,7 +702,7 @@ function handleSearch() {
|
|||
*/
|
||||
function handleReset() {
|
||||
queryParams.orderNo = ''
|
||||
queryParams.userId = ''
|
||||
queryParams.userUid = ''
|
||||
queryParams.orderType = undefined
|
||||
queryParams.status = undefined
|
||||
queryParams.payType = undefined
|
||||
|
|
@ -818,8 +819,8 @@ async function handleExport() {
|
|||
if (queryParams.orderNo) {
|
||||
params.orderNo = queryParams.orderNo
|
||||
}
|
||||
if (queryParams.userId) {
|
||||
params.userId = Number(queryParams.userId)
|
||||
if (queryParams.userUid) {
|
||||
params.userUid = queryParams.userUid
|
||||
}
|
||||
if (queryParams.orderType !== undefined && queryParams.orderType !== '') {
|
||||
params.orderType = Number(queryParams.orderType)
|
||||
|
|
@ -920,6 +921,11 @@ onMounted(() => {
|
|||
color: var(--text-primary, #303133);
|
||||
}
|
||||
|
||||
.user-info .uid {
|
||||
font-size: 12px;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.user-info .phone {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary, #909399);
|
||||
|
|
|
|||
|
|
@ -161,12 +161,16 @@
|
|||
</el-table-column>
|
||||
|
||||
<!-- 操作 -->
|
||||
<el-table-column label="操作" width="200" fixed="right" align="center">
|
||||
<el-table-column label="操作" width="250" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link size="small" @click="handleViewDetail(row)">
|
||||
<el-icon><View /></el-icon>
|
||||
详情
|
||||
</el-button>
|
||||
<el-button type="info" link size="small" @click="handleViewSubordinates(row)">
|
||||
<el-icon><User /></el-icon>
|
||||
下级
|
||||
</el-button>
|
||||
<el-button type="warning" link size="small" @click="handleChangeLevel(row)">
|
||||
<el-icon><Edit /></el-icon>
|
||||
等级
|
||||
|
|
@ -282,6 +286,64 @@
|
|||
</div>
|
||||
</el-drawer>
|
||||
|
||||
<!-- 下级用户对话框 -->
|
||||
<el-dialog
|
||||
v-model="state.subDialogVisible"
|
||||
:title="`下级用户 - ${state.subParentNickname}(${state.subParentUid})`"
|
||||
width="900px"
|
||||
:close-on-click-modal="true"
|
||||
destroy-on-close
|
||||
>
|
||||
<el-table
|
||||
v-loading="state.subLoading"
|
||||
:data="state.subTableData"
|
||||
stripe
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column prop="uid" label="UID" width="120" />
|
||||
<el-table-column prop="nickname" label="昵称" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="phone" label="手机号" width="130">
|
||||
<template #default="{ row }">
|
||||
{{ row.phone || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="用户等级" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getLevelTagType(row.userLevel)">
|
||||
{{ row.userLevelName || getLevelName(row.userLevel) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="balance" label="余额" width="100" align="right">
|
||||
<template #default="{ row }">
|
||||
<span class="balance-text">¥{{ formatMoney(row.balance) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="80" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === 1 ? 'success' : 'danger'" size="small">
|
||||
{{ row.statusName || (row.status === 1 ? '正常' : '禁用') }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="注册时间" width="170" align="center" />
|
||||
</el-table>
|
||||
|
||||
<div class="pagination-wrapper" v-if="state.subTotal > 0">
|
||||
<el-pagination
|
||||
v-model:current-page="state.subPage"
|
||||
v-model:page-size="state.subPageSize"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
:total="state.subTotal"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@size-change="(s: number) => { state.subPageSize = s; state.subPage = 1; loadSubordinates() }"
|
||||
@current-change="(p: number) => { state.subPage = p; loadSubordinates() }"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<el-empty v-if="!state.subLoading && state.subTableData.length === 0" description="暂无下级用户" />
|
||||
</el-dialog>
|
||||
|
||||
<!-- 等级修改对话框 -->
|
||||
<el-dialog
|
||||
v-model="state.levelDialogVisible"
|
||||
|
|
@ -410,6 +472,15 @@ interface UserPageState {
|
|||
levelFormData: LevelFormData
|
||||
levelFormLoading: boolean
|
||||
exportLoading: boolean
|
||||
subDialogVisible: boolean
|
||||
subLoading: boolean
|
||||
subTableData: UserItem[]
|
||||
subTotal: number
|
||||
subPage: number
|
||||
subPageSize: number
|
||||
subParentId: number
|
||||
subParentNickname: string
|
||||
subParentUid: string
|
||||
}
|
||||
|
||||
// ============ Refs ============
|
||||
|
|
@ -447,7 +518,16 @@ const state = reactive<UserPageState>({
|
|||
userLevel: ''
|
||||
},
|
||||
levelFormLoading: false,
|
||||
exportLoading: false
|
||||
exportLoading: false,
|
||||
subDialogVisible: false,
|
||||
subLoading: false,
|
||||
subTableData: [],
|
||||
subTotal: 0,
|
||||
subPage: 1,
|
||||
subPageSize: 10,
|
||||
subParentId: 0,
|
||||
subParentNickname: '',
|
||||
subParentUid: ''
|
||||
})
|
||||
|
||||
// ============ Form Rules ============
|
||||
|
|
@ -604,6 +684,37 @@ function handleViewDetail(row: UserItem) {
|
|||
loadUserDetail(row.id)
|
||||
}
|
||||
|
||||
function handleViewSubordinates(row: UserItem) {
|
||||
state.subParentId = row.id
|
||||
state.subParentNickname = row.nickname
|
||||
state.subParentUid = row.uid
|
||||
state.subPage = 1
|
||||
state.subDialogVisible = true
|
||||
loadSubordinates()
|
||||
}
|
||||
|
||||
async function loadSubordinates() {
|
||||
state.subLoading = true
|
||||
try {
|
||||
const res = await getUserList({
|
||||
page: state.subPage,
|
||||
pageSize: state.subPageSize,
|
||||
parentUserId: state.subParentId
|
||||
})
|
||||
if (res.code === 0) {
|
||||
state.subTableData = res.data?.list || []
|
||||
state.subTotal = res.data?.total || 0
|
||||
} else {
|
||||
throw new Error(res.message || '获取下级用户列表失败')
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : '获取下级用户列表失败'
|
||||
ElMessage.error(message)
|
||||
} finally {
|
||||
state.subLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleStatusChange(row: UserItemWithLoading, status: number) {
|
||||
row.statusLoading = true
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -242,18 +242,47 @@ public class InviteService : IInviteService
|
|||
})
|
||||
.ToListAsync();
|
||||
|
||||
// 获取每个下级用户贡献的佣金总额
|
||||
var userIds = invitedUsers.Select(u => u.Id).ToList();
|
||||
var commissions = await _dbContext.Commissions
|
||||
// 获取每个下级用户贡献的佣金总额(含直接下级 + 间接下级)
|
||||
var directUserIds = invitedUsers.Select(u => u.Id).ToList();
|
||||
|
||||
// 查询间接下级(下下级),并记录其直属上级的映射关系
|
||||
var indirectUsers = await _dbContext.Users
|
||||
.AsNoTracking()
|
||||
.Where(c => c.UserId == userId && userIds.Contains(c.FromUserId) && !c.IsDeleted)
|
||||
.GroupBy(c => c.FromUserId)
|
||||
.Select(g => new
|
||||
.Where(u => u.ParentUserId.HasValue && directUserIds.Contains(u.ParentUserId.Value) && !u.IsDeleted)
|
||||
.Select(u => new { u.Id, ParentUserId = u.ParentUserId!.Value })
|
||||
.ToListAsync();
|
||||
|
||||
var indirectToDirectMap = indirectUsers.ToDictionary(u => u.Id, u => u.ParentUserId);
|
||||
var allRelatedUserIds = directUserIds.Concat(indirectUsers.Select(u => u.Id)).ToList();
|
||||
|
||||
// 查询当前用户从这些下级/下下级获得的所有佣金
|
||||
var commissionRecords = await _dbContext.Commissions
|
||||
.AsNoTracking()
|
||||
.Where(c => c.UserId == userId && allRelatedUserIds.Contains(c.FromUserId) && !c.IsDeleted)
|
||||
.ToListAsync();
|
||||
|
||||
// 将间接下级的佣金归属到对应的直接下级名下
|
||||
var commissions = new Dictionary<long, decimal>();
|
||||
foreach (var c in commissionRecords)
|
||||
{
|
||||
long directUserId;
|
||||
if (directUserIds.Contains(c.FromUserId))
|
||||
{
|
||||
FromUserId = g.Key,
|
||||
TotalCommission = g.Sum(c => c.CommissionAmount)
|
||||
})
|
||||
.ToDictionaryAsync(x => x.FromUserId, x => x.TotalCommission);
|
||||
directUserId = c.FromUserId;
|
||||
}
|
||||
else if (indirectToDirectMap.TryGetValue(c.FromUserId, out var parentId))
|
||||
{
|
||||
directUserId = parentId;
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!commissions.ContainsKey(directUserId))
|
||||
commissions[directUserId] = 0;
|
||||
commissions[directUserId] += c.CommissionAmount;
|
||||
}
|
||||
|
||||
// 组装结果
|
||||
var records = invitedUsers.Select(u => new InviteRecordDto
|
||||
|
|
|
|||
|
|
@ -286,15 +286,14 @@ onLoad((options) => {
|
|||
<text>上一题</text>
|
||||
</view>
|
||||
<view
|
||||
v-if="!isLast"
|
||||
class="action-btn action-next"
|
||||
:class="{ 'action-disabled': isLast }"
|
||||
@click="goNext"
|
||||
>
|
||||
<text>下一题</text>
|
||||
</view>
|
||||
<view
|
||||
v-else
|
||||
class="action-btn action-next"
|
||||
class="action-btn action-submit"
|
||||
:class="{ 'btn-loading': submitting }"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
|
|
@ -596,6 +595,19 @@ onLoad((options) => {
|
|||
color: $text-white;
|
||||
}
|
||||
|
||||
&.action-disabled {
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.action-submit {
|
||||
background-color: #FF6B60;
|
||||
|
||||
text {
|
||||
color: $text-white;
|
||||
}
|
||||
|
||||
&.btn-loading {
|
||||
opacity: 0.7;
|
||||
pointer-events: none;
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ const { statusBarHeight, navbarHeight, totalNavbarHeight } = useNavbar()
|
|||
const WITHDRAW_STATUS = {
|
||||
APPLYING: 1, // 申请中
|
||||
PROCESSING: 2, // 提现中
|
||||
CANCELLED: 3, // 已取消
|
||||
COMPLETED: 4 // 已提现
|
||||
COMPLETED: 3, // 已提现
|
||||
CANCELLED: 4 // 已取消
|
||||
}
|
||||
|
||||
// 页面状态
|
||||
|
|
@ -363,7 +363,7 @@ onReachBottom(() => {
|
|||
onShareAppMessage(() => ({
|
||||
title: '邀请您加入学业邑规划',
|
||||
path: `/pages/index/index?inviterId=${userStore.userId}`,
|
||||
imageUrl: '/static/logo.png'
|
||||
imageUrl: '/static/ic_share.png'
|
||||
}))
|
||||
|
||||
onShow(() => {
|
||||
|
|
|
|||
BIN
uniapp/static/ic_share.png
Normal file
BIN
uniapp/static/ic_share.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 930 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 4.8 MiB |
Loading…
Reference in New Issue
Block a user