逻辑优化

This commit is contained in:
18631081161 2026-03-27 21:54:44 +08:00
parent e3d91cc13b
commit cb14edc316
14 changed files with 223 additions and 29 deletions

View File

@ -20,6 +20,11 @@ public class OrderDto
/// </summary>
public long UserId { get; set; }
/// <summary>
/// 用户UID
/// </summary>
public string? UserUid { get; set; }
/// <summary>
/// 用户昵称
/// </summary>

View File

@ -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>

View File

@ -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>

View File

@ -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)
{

View File

@ -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))
{

View File

@ -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
/** 订单状态筛选 */

View File

@ -65,6 +65,8 @@ export interface UserDetail extends UserItem {
*
*/
export interface UserQuery extends PagedRequest {
/** 上级用户ID查询下级列表 */
parentUserId?: number
/** 用户UID模糊搜索 */
uid?: string
/** 手机号(模糊搜索) */

View File

@ -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);

View File

@ -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 {

View File

@ -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

View File

@ -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;

View File

@ -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

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