fix: 多项修复和优化
Some checks failed
continuous-integration/drone/push Build is failing

- 预约详情: 隐藏用户手机、修复性别/年级映射、添加家庭氛围和期望字段、格式化日期时间
- 邀请页面: 规则弹窗文字间距修复、提现记录红色标题栏、邀请记录红色标题栏+边框、提现记录弹窗列宽优化
- 系统配置: 邀请规则改为多行文本框
- 邀请绑定: 添加前后端完整链路日志用于排查上下级绑定问题
- 首页: 专业测评区域改为横向滚动、更多区域改为全图片模式
This commit is contained in:
zpc 2026-03-25 14:55:37 +08:00
parent e5e63bd7f2
commit c22a743eb0
11 changed files with 190 additions and 103 deletions

View File

@ -84,4 +84,14 @@ public class BookingDetailDto : BookingDto
/// 更新时间
/// </summary>
public DateTime UpdateTime { get; set; }
/// <summary>
/// 家庭氛围
/// </summary>
public string? FamilyAtmosphere { get; set; }
/// <summary>
/// 期望
/// </summary>
public string? Expectation { get; set; }
}

View File

@ -42,6 +42,7 @@ public class PlannerService : IPlannerService
/// </summary>
private static readonly Dictionary<int, string> GenderNames = new()
{
{ 0, "未填写" },
{ 1, "男" },
{ 2, "女" }
};
@ -51,18 +52,12 @@ public class PlannerService : IPlannerService
/// </summary>
private static readonly Dictionary<int, string> GradeNames = new()
{
{ 1, "一年级" },
{ 2, "二年级" },
{ 3, "三年级" },
{ 4, "四年级" },
{ 5, "五年级" },
{ 6, "六年级" },
{ 7, "初一" },
{ 8, "初二" },
{ 9, "初三" },
{ 10, "高一" },
{ 11, "高二" },
{ 12, "高三" }
{ 1, "小学" },
{ 2, "初中" },
{ 3, "高中" },
{ 4, "大专" },
{ 5, "本科" },
{ 6, "研究生及以上" }
};
@ -405,6 +400,8 @@ public class PlannerService : IPlannerService
ScoreBiology = booking.ScoreBiology,
ScoreGeography = booking.ScoreGeography,
ScorePolitics = booking.ScorePolitics,
FamilyAtmosphere = booking.FamilyAtmosphere,
Expectation = booking.Expectation,
Status = booking.Status,
StatusName = GetBookingStatusName(booking.Status),
OrderAmount = booking.Order?.Amount,

View File

@ -74,6 +74,8 @@ export interface BookingDetail extends BookingItem {
scoreBiology: number | null
scoreGeography: number | null
scorePolitics: number | null
familyAtmosphere: string | null
expectation: string | null
plannerIntroduction: string
orderAmount: number | null
orderStatus: number | null

View File

@ -98,6 +98,14 @@
</el-form-item>
<el-form-item label="配置值">
<el-input
v-if="isTextareaConfig(state.editingItem?.configKey || '')"
v-model="state.editValue"
type="textarea"
:rows="6"
:placeholder="getPlaceholder(state.editingItem?.configType || '')"
/>
<el-input
v-else
v-model="state.editValue"
:placeholder="getPlaceholder(state.editingItem?.configType || '')"
clearable
@ -201,6 +209,9 @@ const RICH_TEXT_CONFIG_KEYS = ['user_agreement', 'privacy_policy', 'about_us_con
//
const IMAGE_CONFIG_KEYS = ['service_qrcode']
//
const TEXTAREA_CONFIG_KEYS = ['invite_rule']
// 使
const HIDDEN_CONFIG_KEYS = ['service_phone', 'service_wechat', 'about_us_content', 'assessment_price']
@ -230,6 +241,13 @@ function isRichTextConfig(configKey: string): boolean {
return RICH_TEXT_CONFIG_KEYS.includes(configKey)
}
/**
* 判断是否为多行文本配置
*/
function isTextareaConfig(configKey: string): boolean {
return TEXTAREA_CONFIG_KEYS.includes(configKey)
}
/**
* 判断是否为图片配置
*/

View File

@ -102,7 +102,6 @@
<el-descriptions-item label="订单号">{{ detailData.orderNo }}</el-descriptions-item>
<el-descriptions-item label="用户UID">{{ detailData.userUid }}</el-descriptions-item>
<el-descriptions-item label="用户昵称">{{ detailData.userNickname }}</el-descriptions-item>
<el-descriptions-item label="用户手机">{{ detailData.userPhone }}</el-descriptions-item>
<el-descriptions-item label="规划师">
<div class="planner-info">
<el-avatar :src="detailData.plannerAvatar" :size="40" />
@ -112,7 +111,7 @@
</div>
</div>
</el-descriptions-item>
<el-descriptions-item label="预约日期">{{ detailData.bookingDate }}</el-descriptions-item>
<el-descriptions-item label="预约日期">{{ formatBookingDateTime(detailData.bookingDate, detailData.bookingTime) }}</el-descriptions-item>
<el-descriptions-item label="学生姓名">{{ detailData.name }}</el-descriptions-item>
<el-descriptions-item label="性别">{{ detailData.genderName }}</el-descriptions-item>
<el-descriptions-item label="学生年级">{{ detailData.gradeName }}</el-descriptions-item>
@ -126,10 +125,12 @@
<el-descriptions-item label="生物成绩">{{ detailData.scoreBiology ?? '-' }}</el-descriptions-item>
<el-descriptions-item label="地理成绩">{{ detailData.scoreGeography ?? '-' }}</el-descriptions-item>
<el-descriptions-item label="政治成绩">{{ detailData.scorePolitics ?? '-' }}</el-descriptions-item>
<el-descriptions-item label="家庭氛围">{{ detailData.familyAtmosphere || '-' }}</el-descriptions-item>
<el-descriptions-item label="期望">{{ detailData.expectation || '-' }}</el-descriptions-item>
<el-descriptions-item label="状态">
<el-tag :type="getStatusType(detailData.status)">{{ detailData.statusName }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ detailData.createTime }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ formatDateTime(detailData.createTime) }}</el-descriptions-item>
</el-descriptions>
</el-drawer>
@ -211,6 +212,22 @@ const statusForm = reactive({
status: 0
})
//
const getStatusType = (status: number): '' | 'success' | 'warning' | 'info' | 'danger' => {
// T
const formatDateTime = (dt: string | undefined): string => {
if (!dt) return '-'
return dt.substring(0, 19).replace('T', ' ')
}
// +
const formatBookingDateTime = (date: string | undefined, time: string | undefined): string => {
if (!date) return '-'
const d = date.substring(0, 10)
return time ? `${d} ${time}` : d
}
//
const getStatusType = (status: number): '' | 'success' | 'warning' | 'info' | 'danger' => {
const map: Record<number, '' | 'success' | 'warning' | 'info' | 'danger'> = {

View File

@ -8,10 +8,10 @@ using Microsoft.AspNetCore.Mvc;
namespace MiAssessment.Api.Controllers;
/// <summary>
/// 认证控制器 - 处理用户登录、注册、Token刷新和手机号绑定
/// <EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> - <20><><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD><C3BB><EFBFBD>¼<EFBFBD><C2BC>ע<EFBFBD>ᡢTokenˢ<6E>º<EFBFBD><C2BA>ֻ<EFBFBD><D6BB>Ű<EFBFBD>
/// </summary>
/// <remarks>
/// 提供微信小程序登录、手机号验证码登录、Token刷新、手机号绑定等功能
/// <EFBFBD>ṩ΢<EFBFBD><EFBFBD>С<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>¼<EFBFBD><EFBFBD><EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD><EFBFBD>¼<EFBFBD><EFBFBD>Tokenˢ<EFBFBD>¡<EFBFBD><EFBFBD>ֻ<EFBFBD><EFBFBD>Ű󶨵ȹ<EFBFBD><EFBFBD><EFBFBD>
/// </remarks>
[ApiController]
[Route("api")]
@ -29,23 +29,26 @@ public class AuthController : ControllerBase
}
/// <summary>
/// 微信小程序登录
/// ΢<EFBFBD><EFBFBD>С<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>¼
/// </summary>
/// <remarks>
/// POST /api/login
///
/// 使用微信小程序授权code进行登录返回双TokenAccess Token + Refresh Token
/// ʹ<EFBFBD><EFBFBD>΢<EFBFBD><EFBFBD>С<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ȩcode<EFBFBD><EFBFBD><EFBFBD>е<EFBFBD>¼<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˫Token<EFBFBD><EFBFBD>Access Token + Refresh Token<65><6E>
/// Requirements: 1.1, 1.2, 6.1
/// </remarks>
/// <param name="request">微信登录请求参数,包含授权code</param>
/// <returns>登录成功返回LoginResponse包含accessToken、refreshToken、expiresIn失败返回错误信息</returns>
/// <param name="request">΢<EFBFBD>ŵ<EFBFBD>¼<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ȩcode</param>
/// <returns><EFBFBD><EFBFBD>¼<EFBFBD>ɹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>LoginResponse<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>accessToken<EFBFBD><EFBFBD>refreshToken<EFBFBD><EFBFBD>expiresIn<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʧ<EFBFBD>ܷ<EFBFBD><EFBFBD>ش<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϣ</returns>
[HttpPost("login")]
[ProducesResponseType(typeof(ApiResponse<LoginResponse>), StatusCodes.Status200OK)]
public async Task<ApiResponse<LoginResponse>> WechatMiniProgramLogin([FromBody] WechatLoginRequest request)
{
_logger.LogInformation("[AuthController] 收到登录请求: PidStr={PidStr}, Pid={Pid}, ClickId={ClickId}",
request?.PidStr, request?.Pid, request?.ClickId);
if (request == null || string.IsNullOrWhiteSpace(request.Code))
{
return ApiResponse<LoginResponse>.Fail("授权code不能为空");
return ApiResponse<LoginResponse>.Fail("<EFBFBD><EFBFBD>Ȩcode<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD>");
}
var result = await _authService.WechatMiniProgramLoginAsync(
@ -56,41 +59,41 @@ public class AuthController : ControllerBase
if (result.Success && result.LoginResponse != null)
{
_logger.LogInformation("WeChat login successful: UserId={UserId}", result.UserId);
return ApiResponse<LoginResponse>.Success(result.LoginResponse, "登录成功");
return ApiResponse<LoginResponse>.Success(result.LoginResponse, "<EFBFBD><EFBFBD>¼<EFBFBD>ɹ<EFBFBD>");
}
_logger.LogWarning("WeChat login failed: {Error}", result.ErrorMessage);
return ApiResponse<LoginResponse>.Fail(result.ErrorMessage ?? "登录失败");
return ApiResponse<LoginResponse>.Fail(result.ErrorMessage ?? "<EFBFBD><EFBFBD>¼ʧ<EFBFBD><EFBFBD>");
}
/// <summary>
/// 手机号验证码登录
/// <EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD><EFBFBD>¼
/// </summary>
/// <remarks>
/// POST /api/mobileLogin
///
/// 使用手机号和验证码进行登录返回双TokenAccess Token + Refresh Token
/// ʹ<EFBFBD><EFBFBD><EFBFBD>ֻ<EFBFBD><EFBFBD>ź<EFBFBD><EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD><EFBFBD><EFBFBD>е<EFBFBD>¼<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˫Token<EFBFBD><EFBFBD>Access Token + Refresh Token<65><6E>
/// Requirements: 1.1, 1.2, 6.1
/// </remarks>
/// <param name="request">手机号登录请求参数,包含手机号和验证码</param>
/// <returns>登录成功返回LoginResponse包含accessToken、refreshToken、expiresIn失败返回错误信息</returns>
/// <param name="request"><EFBFBD>ֻ<EFBFBD><EFBFBD>ŵ<EFBFBD>¼<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֻ<EFBFBD><EFBFBD>ź<EFBFBD><EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD></param>
/// <returns><EFBFBD><EFBFBD>¼<EFBFBD>ɹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>LoginResponse<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>accessToken<EFBFBD><EFBFBD>refreshToken<EFBFBD><EFBFBD>expiresIn<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʧ<EFBFBD>ܷ<EFBFBD><EFBFBD>ش<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϣ</returns>
[HttpPost("mobileLogin")]
[ProducesResponseType(typeof(ApiResponse<LoginResponse>), StatusCodes.Status200OK)]
public async Task<ApiResponse<LoginResponse>> MobileLogin([FromBody] MobileLoginRequest request)
{
if (request == null)
{
return ApiResponse<LoginResponse>.Fail("请求参数不能为空");
return ApiResponse<LoginResponse>.Fail("<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD>");
}
if (string.IsNullOrWhiteSpace(request.Mobile))
{
return ApiResponse<LoginResponse>.Fail("手机号不能为空");
return ApiResponse<LoginResponse>.Fail("<EFBFBD>ֻ<EFBFBD><EFBFBD>Ų<EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD>");
}
if (string.IsNullOrWhiteSpace(request.Code))
{
return ApiResponse<LoginResponse>.Fail("验证码不能为空");
return ApiResponse<LoginResponse>.Fail("<EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD>");
}
var result = await _authService.MobileLoginAsync(
@ -102,24 +105,24 @@ public class AuthController : ControllerBase
if (result.Success && result.LoginResponse != null)
{
_logger.LogInformation("Mobile login successful: UserId={UserId}", result.UserId);
return ApiResponse<LoginResponse>.Success(result.LoginResponse, "登录成功");
return ApiResponse<LoginResponse>.Success(result.LoginResponse, "<EFBFBD><EFBFBD>¼<EFBFBD>ɹ<EFBFBD>");
}
_logger.LogWarning("Mobile login failed: {Error}", result.ErrorMessage);
return ApiResponse<LoginResponse>.Fail(result.ErrorMessage ?? "登录失败");
return ApiResponse<LoginResponse>.Fail(result.ErrorMessage ?? "<EFBFBD><EFBFBD>¼ʧ<EFBFBD><EFBFBD>");
}
/// <summary>
/// 刷新 Token
/// ˢ<EFBFBD><EFBFBD> Token
/// </summary>
/// <remarks>
/// POST /api/refresh
///
/// 使用 Refresh Token 获取新的 Access Token 和 Refresh Token
/// ʹ<EFBFBD><EFBFBD> Refresh Token <20><>ȡ<EFBFBD>µ<EFBFBD> Access Token <20><> Refresh Token
/// Requirements: 2.1, 2.2, 2.3, 2.4
/// </remarks>
/// <param name="request">刷新请求,包含 Refresh Token</param>
/// <returns>刷新成功返回新的 LoginResponse失败返回错误信息</returns>
/// <param name="request">ˢ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>󣬰<EFBFBD><EFBFBD><EFBFBD> Refresh Token</param>
/// <returns>ˢ<EFBFBD>³ɹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>µ<EFBFBD> LoginResponse<73><65>ʧ<EFBFBD>ܷ<EFBFBD><DCB7>ش<EFBFBD><D8B4><EFBFBD><EFBFBD><EFBFBD>Ϣ</returns>
[HttpPost("refresh")]
[AllowAnonymous]
[ProducesResponseType(typeof(ApiResponse<LoginResponse>), StatusCodes.Status200OK)]
@ -127,7 +130,7 @@ public class AuthController : ControllerBase
{
if (request == null || string.IsNullOrWhiteSpace(request.RefreshToken))
{
return ApiResponse<LoginResponse>.Fail("刷新令牌不能为空");
return ApiResponse<LoginResponse>.Fail("ˢ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ʋ<EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD>");
}
var clientIp = GetClientIp();
@ -136,27 +139,27 @@ public class AuthController : ControllerBase
if (result.Success && result.LoginResponse != null)
{
_logger.LogInformation("Token refresh successful: UserId={UserId}", result.LoginResponse.UserId);
return ApiResponse<LoginResponse>.Success(result.LoginResponse, "刷新成功");
return ApiResponse<LoginResponse>.Success(result.LoginResponse, "ˢ<EFBFBD>³ɹ<EFBFBD>");
}
_logger.LogWarning("Token refresh failed: {Error}", result.ErrorMessage);
// 根据错误类型返回不同的状态码
// -1 表示未登录/Token无效前端需要跳转登录页
return ApiResponse<LoginResponse>.Fail(result.ErrorMessage ?? "刷新失败", -1);
// <EFBFBD><EFBFBD><EFBFBD>ݴ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͷ<EFBFBD><EFBFBD>ز<EFBFBD>ͬ<EFBFBD><EFBFBD>״̬<EFBFBD><EFBFBD>
// -1 <EFBFBD><EFBFBD>ʾδ<EFBFBD><EFBFBD>¼/Token<65><6E>Ч<EFBFBD><D0A7>ǰ<EFBFBD><C7B0><EFBFBD><EFBFBD>Ҫ<EFBFBD><D2AA>ת<EFBFBD><D7AA>¼ҳ
return ApiResponse<LoginResponse>.Fail(result.ErrorMessage ?? "ˢ<EFBFBD><EFBFBD>ʧ<EFBFBD><EFBFBD>", -1);
}
/// <summary>
/// 退出登录(撤销 Token
/// <EFBFBD>˳<EFBFBD><EFBFBD><EFBFBD>¼<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Token<65><6E>
/// </summary>
/// <remarks>
/// POST /api/logout
///
/// 撤销用户的 Refresh Token使其失效
/// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD><EFBFBD><EFBFBD> Refresh Token<65><6E>ʹ<EFBFBD><CAB9>ʧЧ
/// Requirements: 4.4
/// </remarks>
/// <param name="request">退出请求,包含要撤销的 Refresh Token可选不传则撤销当前用户所有Token</param>
/// <returns>退出成功返回成功消息</returns>
/// <param name="request"><EFBFBD>˳<EFBFBD><EFBFBD><EFBFBD><EFBFBD>󣬰<EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Refresh Token<65><6E><EFBFBD><EFBFBD>ѡ<EFBFBD><D1A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǰ<EFBFBD>û<EFBFBD><C3BB><EFBFBD><EFBFBD><EFBFBD>Token<65><6E></param>
/// <returns><EFBFBD>˳<EFBFBD><EFBFBD>ɹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>سɹ<EFBFBD><EFBFBD><EFBFBD>Ϣ</returns>
[HttpPost("logout")]
[Authorize]
[ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)]
@ -169,38 +172,38 @@ public class AuthController : ControllerBase
{
if (request != null && !string.IsNullOrWhiteSpace(request.RefreshToken))
{
// 撤销指定的 Refresh Token
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ָ<EFBFBD><EFBFBD><EFBFBD><EFBFBD> Refresh Token
await _authService.RevokeTokenAsync(request.RefreshToken, clientIp);
_logger.LogInformation("Token revoked: UserId={UserId}", userId);
}
else if (userId.HasValue)
{
// 撤销用户的所有 Refresh Token
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Refresh Token
await _authService.RevokeAllUserTokensAsync(userId.Value, clientIp);
_logger.LogInformation("All tokens revoked: UserId={UserId}", userId);
}
return ApiResponse.Success("退出成功");
return ApiResponse.Success("<EFBFBD>˳<EFBFBD><EFBFBD>ɹ<EFBFBD>");
}
catch (Exception ex)
{
_logger.LogWarning("Logout failed: UserId={UserId}, Error={Error}", userId, ex.Message);
return ApiResponse.Fail("退出失败");
return ApiResponse.Fail("<EFBFBD>˳<EFBFBD>ʧ<EFBFBD><EFBFBD>");
}
}
/// <summary>
/// 微信授权绑定手机号
/// ΢<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><EFBFBD><EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD>
/// </summary>
/// <remarks>
/// POST /api/login_bind_mobile
///
/// 使用微信授权code获取手机号并绑定到当前用户
/// ʹ<EFBFBD><EFBFBD>΢<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ȩcode<EFBFBD><EFBFBD>ȡ<EFBFBD>ֻ<EFBFBD><EFBFBD>Ų<EFBFBD><EFBFBD>󶨵<EFBFBD><EFBFBD><EFBFBD>ǰ<EFBFBD>û<EFBFBD>
/// Requirements: 5.1-5.5
/// </remarks>
/// <param name="request">绑定手机号请求参数,包含微信授权code</param>
/// <returns>绑定成功返回手机号信息,失败返回错误信息</returns>
/// <param name="request"><EFBFBD><EFBFBD><EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>΢<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ȩcode</param>
/// <returns><EFBFBD>󶨳ɹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϣ<EFBFBD><EFBFBD>ʧ<EFBFBD>ܷ<EFBFBD><EFBFBD>ش<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϣ</returns>
[HttpPost("login_bind_mobile")]
[Authorize]
[ProducesResponseType(typeof(ApiResponse<BindMobileResponse>), StatusCodes.Status200OK)]
@ -215,14 +218,14 @@ public class AuthController : ControllerBase
if (request == null || string.IsNullOrWhiteSpace(request.Code))
{
return ApiResponse<BindMobileResponse>.Fail("微信授权code不能为空");
return ApiResponse<BindMobileResponse>.Fail("΢<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ȩcode<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD>");
}
try
{
var result = await _authService.WechatBindMobileAsync(userId.Value, request.Code);
_logger.LogInformation("WeChat bind mobile successful: UserId={UserId}", userId);
return ApiResponse<BindMobileResponse>.Success(result, "绑定成功");
return ApiResponse<BindMobileResponse>.Success(result, "<EFBFBD>󶨳ɹ<EFBFBD>");
}
catch (InvalidOperationException ex)
{
@ -232,16 +235,16 @@ public class AuthController : ControllerBase
}
/// <summary>
/// 验证码绑定手机号
/// <EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD>
/// </summary>
/// <remarks>
/// POST /api/bindMobile
///
/// 使用手机号和验证码绑定手机号到当前用户
/// ʹ<EFBFBD><EFBFBD><EFBFBD>ֻ<EFBFBD><EFBFBD>ź<EFBFBD><EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֻ<EFBFBD><EFBFBD>ŵ<EFBFBD><EFBFBD><EFBFBD>ǰ<EFBFBD>û<EFBFBD>
/// Requirements: 5.1-5.5
/// </remarks>
/// <param name="request">绑定手机号请求参数,包含手机号和验证码</param>
/// <returns>绑定成功返回手机号信息,失败返回错误信息</returns>
/// <param name="request"><EFBFBD><EFBFBD><EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֻ<EFBFBD><EFBFBD>ź<EFBFBD><EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD></param>
/// <returns><EFBFBD>󶨳ɹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϣ<EFBFBD><EFBFBD>ʧ<EFBFBD>ܷ<EFBFBD><EFBFBD>ش<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϣ</returns>
[HttpPost("bindMobile")]
[Authorize]
[ProducesResponseType(typeof(ApiResponse<BindMobileResponse>), StatusCodes.Status200OK)]
@ -256,24 +259,24 @@ public class AuthController : ControllerBase
if (request == null)
{
return ApiResponse<BindMobileResponse>.Fail("请求参数不能为空");
return ApiResponse<BindMobileResponse>.Fail("<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD>");
}
if (string.IsNullOrWhiteSpace(request.Mobile))
{
return ApiResponse<BindMobileResponse>.Fail("手机号不能为空");
return ApiResponse<BindMobileResponse>.Fail("<EFBFBD>ֻ<EFBFBD><EFBFBD>Ų<EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD>");
}
if (string.IsNullOrWhiteSpace(request.Code))
{
return ApiResponse<BindMobileResponse>.Fail("验证码不能为空");
return ApiResponse<BindMobileResponse>.Fail("<EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD>");
}
try
{
var result = await _authService.BindMobileAsync(userId.Value, request.Mobile, request.Code);
_logger.LogInformation("Bind mobile successful: UserId={UserId}", userId);
return ApiResponse<BindMobileResponse>.Success(result, "绑定成功");
return ApiResponse<BindMobileResponse>.Success(result, "<EFBFBD>󶨳ɹ<EFBFBD>");
}
catch (InvalidOperationException ex)
{
@ -283,16 +286,16 @@ public class AuthController : ControllerBase
}
/// <summary>
/// H5绑定手机号(无需验证码)
/// H5<EFBFBD><EFBFBD><EFBFBD>ֻ<EFBFBD><EFBFBD>ţ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>֤<EFBFBD>
/// </summary>
/// <remarks>
/// POST /api/login_bind_mobile_h5
///
/// H5端直接绑定手机号到当前用户,无需短信验证码
/// H5<EFBFBD><EFBFBD>ֱ<EFBFBD>Ӱ<EFBFBD><EFBFBD>ֻ<EFBFBD><EFBFBD>ŵ<EFBFBD><EFBFBD><EFBFBD>ǰ<EFBFBD>û<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD>
/// Requirements: 13.1
/// </remarks>
/// <param name="request">绑定手机号请求参数,包含手机号</param>
/// <returns>绑定成功返回结果,失败返回错误信息</returns>
/// <param name="request"><EFBFBD><EFBFBD><EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD></param>
/// <returns><EFBFBD>󶨳ɹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ؽ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʧ<EFBFBD>ܷ<EFBFBD><EFBFBD>ش<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϣ</returns>
[HttpPost("login_bind_mobile_h5")]
[Authorize]
[ProducesResponseType(typeof(ApiResponse<BindMobileResponse>), StatusCodes.Status200OK)]
@ -307,19 +310,19 @@ public class AuthController : ControllerBase
if (request == null)
{
return ApiResponse<BindMobileResponse>.Fail("请求参数不能为空");
return ApiResponse<BindMobileResponse>.Fail("<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD>");
}
if (string.IsNullOrWhiteSpace(request.Mobile))
{
return ApiResponse<BindMobileResponse>.Fail("手机号不能为空");
return ApiResponse<BindMobileResponse>.Fail("<EFBFBD>ֻ<EFBFBD><EFBFBD>Ų<EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD>");
}
try
{
var result = await _authService.BindMobileH5Async(userId.Value, request.Mobile);
_logger.LogInformation("H5 Bind mobile successful: UserId={UserId}", userId);
return ApiResponse<BindMobileResponse>.Success(result, "绑定成功");
return ApiResponse<BindMobileResponse>.Success(result, "<EFBFBD>󶨳ɹ<EFBFBD>");
}
catch (InvalidOperationException ex)
{
@ -329,16 +332,16 @@ public class AuthController : ControllerBase
}
/// <summary>
/// 记录用户登录
/// <EFBFBD><EFBFBD>¼<EFBFBD>û<EFBFBD><EFBFBD><EFBFBD>¼
/// </summary>
/// <remarks>
/// GET|POST /api/login_record
///
/// 记录用户登录信息包括设备信息和IP地址
/// <EFBFBD><EFBFBD>¼<EFBFBD>û<EFBFBD><EFBFBD><EFBFBD>¼<EFBFBD><EFBFBD>Ϣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϣ<EFBFBD><EFBFBD>IP<EFBFBD><EFBFBD>ַ
/// Requirements: 6.1-6.4
/// </remarks>
/// <param name="request">登录记录请求参数,包含设备信息(可选)</param>
/// <returns>登录记录结果</returns>
/// <param name="request"><EFBFBD><EFBFBD>¼<EFBFBD><EFBFBD>¼<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ѡ<EFBFBD><EFBFBD></param>
/// <returns><EFBFBD><EFBFBD>¼<EFBFBD><EFBFBD>¼<EFBFBD><EFBFBD><EFBFBD></returns>
[HttpPost("login_record")]
[HttpGet("login_record")]
[Authorize]
@ -354,7 +357,7 @@ public class AuthController : ControllerBase
try
{
// 获取客户端IP
// <EFBFBD><EFBFBD>ȡ<EFBFBD>ͻ<EFBFBD><EFBFBD><EFBFBD>IP
var clientIp = GetClientIp();
var result = await _authService.RecordLoginAsync(
@ -374,7 +377,7 @@ public class AuthController : ControllerBase
#region Private Helper Methods
/// <summary>
/// 获取当前登录用户ID
/// <EFBFBD><EFBFBD>ȡ<EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD>¼<EFBFBD>û<EFBFBD>ID
/// </summary>
private long? GetCurrentUserId()
{
@ -387,15 +390,15 @@ public class AuthController : ControllerBase
}
/// <summary>
/// 获取客户端IP地址
/// <EFBFBD><EFBFBD>ȡ<EFBFBD>ͻ<EFBFBD><EFBFBD><EFBFBD>IP<EFBFBD><EFBFBD>ַ
/// </summary>
private string GetClientIp()
{
// 优先从X-Forwarded-For头获取经过代理的情况
// <EFBFBD><EFBFBD><EFBFBD>ȴ<EFBFBD>X-Forwarded-Forͷ<72><CDB7>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
var forwardedFor = HttpContext.Request.Headers["X-Forwarded-For"].FirstOrDefault();
if (!string.IsNullOrWhiteSpace(forwardedFor))
{
// X-Forwarded-For可能包含多个IP取第一个
// X-Forwarded-For<EFBFBD><EFBFBD><EFBFBD>ܰ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>IP<EFBFBD><EFBFBD>ȡ<EFBFBD><EFBFBD>һ<EFBFBD><EFBFBD>
var ip = forwardedFor.Split(',').FirstOrDefault()?.Trim();
if (!string.IsNullOrWhiteSpace(ip))
{
@ -403,18 +406,18 @@ public class AuthController : ControllerBase
}
}
// 从X-Real-IP头获取
// <EFBFBD><EFBFBD>X-Real-IPͷ<50><CDB7>ȡ
var realIp = HttpContext.Request.Headers["X-Real-IP"].FirstOrDefault();
if (!string.IsNullOrWhiteSpace(realIp))
{
return realIp;
}
// 从连接获取
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ӻ<EFBFBD>ȡ
var remoteIp = HttpContext.Connection.RemoteIpAddress;
if (remoteIp != null)
{
// 如果是IPv6的本地回环地址转换为IPv4
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>IPv6<EFBFBD>ı<EFBFBD><EFBFBD>ػػ<EFBFBD><EFBFBD><EFBFBD>ַ<EFBFBD><EFBFBD>ת<EFBFBD><EFBFBD>ΪIPv4
if (remoteIp.IsIPv4MappedToIPv6)
{
return remoteIp.MapToIPv4().ToString();

View File

@ -130,8 +130,8 @@ public class AuthService : IAuthService
if (user == null)
{
// 1.3 <EFBFBD>û<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD>
_logger.LogInformation("[AuthService] <EFBFBD>û<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڣ<EFBFBD><EFBFBD><EFBFBD>ʼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD>...");
// 1.3 新用户,创建并绑定上级
_logger.LogInformation("[AuthService] 用户不存在,开始创建新用户, pid={Pid}", pid);
var createDto = new CreateUserDto
{
OpenId = openId,
@ -140,18 +140,20 @@ public class AuthService : IAuthService
Headimg = await GetDefaultAvatarAsync(openId),
Pid = pid ?? 0
};
_logger.LogInformation("[AuthService] CreateUserDto.Pid={Pid}", createDto.Pid);
user = await _userService.CreateUserAsync(createDto);
_logger.LogInformation("[AuthService] <EFBFBD><EFBFBD><EFBFBD>û<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɹ<EFBFBD>: UserId={UserId}, OpenId={OpenId}", user.Id, openId);
_logger.LogInformation("[AuthService] 新用户创建成功: UserId={UserId}, ParentUserId={ParentUserId}", user.Id, user.ParentUserId);
}
else
{
// 1.4 <20>û<EFBFBD><C3BB><EFBFBD><EFBFBD>ڣ<EFBFBD><DAA3><EFBFBD><EFBFBD><EFBFBD>unionid<69><64><EFBFBD><EFBFBD><EFBFBD>֮ǰΪ<C7B0>գ<EFBFBD>
// 1.4 已有用户,不绑定上级
_logger.LogInformation("[AuthService] 已有用户登录: UserId={UserId}, ParentUserId={ParentUserId}, pid参数={Pid}(忽略)", user.Id, user.ParentUserId, pid);
if (string.IsNullOrWhiteSpace(user.UnionId) && !string.IsNullOrWhiteSpace(unionId))
{
_logger.LogInformation("[AuthService] <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD>unionid: UserId={UserId}", user.Id);
_logger.LogInformation("[AuthService] 更新用户unionid: UserId={UserId}", user.Id);
await _userService.UpdateUserAsync(user.Id, new UpdateUserDto { UnionId = unionId });
_logger.LogInformation("[AuthService] unionid<EFBFBD><EFBFBD><EFBFBD>³ɹ<EFBFBD>");
_logger.LogInformation("[AuthService] unionid更新成功");
}
}

View File

@ -59,9 +59,14 @@ public class UserService : BaseService<User, long>, IUserService
if (dto == null)
throw new ArgumentNullException(nameof(dto));
_logger.LogInformation("[UserService] 创建用户: OpenId={OpenId}, Pid={Pid}", dto.OpenId, dto.Pid);
// <20><><EFBFBD><EFBFBD>ΨһUID
var uid = await GenerateUidAsync();
var parentUserId = dto.Pid > 0 ? (long?)dto.Pid : null;
_logger.LogInformation("[UserService] ParentUserId计算结果: Pid={Pid}, ParentUserId={ParentUserId}", dto.Pid, parentUserId);
var user = new User
{
OpenId = dto.OpenId ?? string.Empty,
@ -70,7 +75,7 @@ public class UserService : BaseService<User, long>, IUserService
Uid = uid,
Nickname = dto.Nickname ?? $"User{Random.Shared.Next(1000, 9999)}",
Avatar = dto.Headimg ?? string.Empty,
ParentUserId = dto.Pid > 0 ? dto.Pid : null,
ParentUserId = parentUserId,
Status = 1,
IsTest = 0,
CreateTime = DateTime.Now,
@ -80,7 +85,8 @@ public class UserService : BaseService<User, long>, IUserService
await _dbSet.AddAsync(user);
await _dbContext.SaveChangesAsync();
_logger.LogInformation($"User created: Id={user.Id}, Uid={user.Uid}, OpenId={user.OpenId}");
_logger.LogInformation("[UserService] 用户创建成功: Id={Id}, Uid={Uid}, OpenId={OpenId}, ParentUserId={ParentUserId}",
user.Id, user.Uid, user.OpenId, user.ParentUserId);
return user;
}

View File

@ -40,27 +40,35 @@
parseInviterFromLaunch(options) {
if (!options) return
console.log('[App] parseInviterFromLaunch 原始options:', JSON.stringify(options))
let inviterId = null
// 1. scene inviter={userId}
if (options.scene) {
const scene = decodeURIComponent(options.scene)
console.log('扫码 scene:', scene)
console.log('[App] 扫码 scene 解码后:', scene)
const match = scene.match(/inviter=(\d+)/)
if (match) {
inviterId = match[1]
console.log('[App] 从scene解析到inviterId:', inviterId)
} else {
console.log('[App] scene中未匹配到inviter参数')
}
}
// 2. inviterId
if (!inviterId && options.inviterId) {
inviterId = options.inviterId
console.log('[App] 从query参数获取inviterId:', inviterId)
}
// ID
if (inviterId) {
console.log('检测到邀请人ID:', inviterId)
console.log('[App] 存储inviterId到Storage:', inviterId)
uni.setStorageSync('inviterId', inviterId)
} else {
console.log('[App] 未检测到邀请人参数')
}
}
}

View File

@ -697,7 +697,10 @@ onMounted(() => { userStore.restoreFromStorage() })
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: $spacing-lg;
margin: -#{$spacing-lg} -#{$spacing-lg} $spacing-lg;
padding: $spacing-md $spacing-lg;
background-color: #FF4757;
border-radius: $border-radius-lg $border-radius-lg 0 0;
.commission-title {
font-size: $font-size-lg;
@ -786,23 +789,25 @@ onMounted(() => { userStore.restoreFromStorage() })
margin: 0 $spacing-lg;
background-color: $bg-white;
border-radius: $border-radius-lg;
padding: $spacing-lg;
overflow: hidden;
min-height: 300rpx;
border: 2rpx solid #FF4D4F;
.record-header {
margin-bottom: $spacing-md;
background-color: #FF4D4F;
padding: $spacing-md $spacing-lg;
.record-title {
font-size: $font-size-lg;
font-weight: $font-weight-bold;
color: #FF4D4F;
color: $text-white;
}
}
.record-table-header {
display: flex;
align-items: center;
padding: $spacing-sm 0;
padding: $spacing-sm $spacing-lg;
border-bottom: 1rpx solid $border-light;
text {
@ -816,7 +821,7 @@ onMounted(() => { userStore.restoreFromStorage() })
.record-row {
display: flex;
align-items: center;
padding: $spacing-md 0;
padding: $spacing-md $spacing-lg;
border-bottom: 1rpx solid $border-light;
&:last-child {
@ -914,13 +919,19 @@ onMounted(() => { userStore.restoreFromStorage() })
.rule-popup {
.popup-body {
max-height: 500rpx;
padding: 0 $spacing-lg $spacing-lg;
box-sizing: border-box;
}
.rule-content {
padding-right: $spacing-xs;
.rule-text {
font-size: $font-size-md;
color: $text-color;
line-height: 1.8;
word-break: break-all;
white-space: pre-wrap;
}
}
}
@ -1086,9 +1097,12 @@ onMounted(() => { userStore.restoreFromStorage() })
//
.wr-popup {
width: 680rpx;
.wr-body {
max-height: 600rpx;
padding: 0 $spacing-lg $spacing-lg;
padding: 0 $spacing-md $spacing-lg;
box-sizing: border-box;
.wr-table-header {
display: flex;
@ -1120,17 +1134,17 @@ onMounted(() => { userStore.restoreFromStorage() })
}
.wr-col-time {
width: 40%;
flex: 2;
text-align: left;
}
.wr-col-amount {
width: 30%;
flex: 1;
text-align: center;
}
.wr-col-status {
width: 30%;
flex: 1;
text-align: right;
}

View File

@ -128,16 +128,22 @@ async function handleGetPhoneNumber(e) {
// ID
const inviterId = uni.getStorageSync('inviterId')
console.log('[Login] 从Storage读取inviterId:', inviterId)
if (inviterId) {
loginData.pid = String(inviterId)
console.log('[Login] 设置loginData.pid:', loginData.pid)
} else {
console.log('[Login] 无inviterId不传pid')
}
console.log('[Login] 发送登录请求, loginData:', JSON.stringify(loginData))
const res = await post('/login', loginData, { needAuth: false })
uni.hideLoading()
if (res && res.code === 0 && res.data) {
// ID
console.log('[Login] 登录成功, userId:', res.data.userId)
uni.removeStorageSync('inviterId')
// LoginResponse token userId
@ -219,16 +225,20 @@ async function handleLogin() {
// ID
const inviterId = uni.getStorageSync('inviterId')
console.log('[Login-backup] 从Storage读取inviterId:', inviterId)
if (inviterId) {
loginData.pid = String(inviterId)
console.log('[Login-backup] 设置loginData.pid:', loginData.pid)
}
console.log('[Login-backup] 发送登录请求, loginData:', JSON.stringify(loginData))
const res = await post('/login', loginData, { needAuth: false })
uni.hideLoading()
if (res && res.code === 0 && res.data) {
// ID
console.log('[Login-backup] 登录成功, userId:', res.data.userId)
uni.removeStorageSync('inviterId')
// LoginResponse token userId