fix: 修复更名退款逻辑、审核列表显示及更名消耗确认提示
- 首次更名审核被拒不再退款,首次免费未扣费不产生退款 - 审核列表提交者列改用 UID,当前用户名列显示当前用户名 - 更名消耗提示改为确认弹窗,首次/非首次动态显示不同文案
This commit is contained in:
@ -50,17 +50,17 @@ func (ac *AuditController) ListAudits(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 为每个审核记录附加提交者的用户名
|
// 为每个审核记录附加提交者的当前用户名
|
||||||
type auditItem struct {
|
type auditItem struct {
|
||||||
model.AuditSubmission
|
model.AuditSubmission
|
||||||
SubmitterUsername string `json:"submitter_username"`
|
CurrentUsername string `json:"current_username"`
|
||||||
}
|
}
|
||||||
|
|
||||||
items := make([]auditItem, 0, len(result.Items))
|
items := make([]auditItem, 0, len(result.Items))
|
||||||
for _, a := range result.Items {
|
for _, a := range result.Items {
|
||||||
item := auditItem{AuditSubmission: a}
|
item := auditItem{AuditSubmission: a}
|
||||||
if user, err := ac.userRepo.FindByID(a.UserID); err == nil {
|
if user, err := ac.userRepo.FindByID(a.UserID); err == nil {
|
||||||
item.SubmitterUsername = user.Username
|
item.CurrentUsername = user.Username
|
||||||
}
|
}
|
||||||
items = append(items, item)
|
items = append(items, item)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -58,15 +58,23 @@ func (sc *SettingsController) SettingsPage(c *gin.Context) {
|
|||||||
// 查询待审核类型(用于前端显示审核提示)
|
// 查询待审核类型(用于前端显示审核提示)
|
||||||
pendingTypes, _ := sc.auditService.GetPendingTypes(uid)
|
pendingTypes, _ := sc.auditService.GetPendingTypes(uid)
|
||||||
|
|
||||||
|
// 是否已完成首次改名(用于前端确认弹窗显示消耗提示)
|
||||||
|
hasCompletedRename := false
|
||||||
|
if sc.levelSvc != nil {
|
||||||
|
completed, _ := sc.levelSvc.HasCompletedTask(uid, "username")
|
||||||
|
hasCompletedRename = completed
|
||||||
|
}
|
||||||
|
|
||||||
data := gin.H{
|
data := gin.H{
|
||||||
"Title": "个人设置",
|
"Title": "个人设置",
|
||||||
"ExtraCSS": "/static/css/settings.css",
|
"ExtraCSS": "/static/css/settings.css",
|
||||||
"ActiveTab": tab,
|
"ActiveTab": tab,
|
||||||
"User": user,
|
"User": user,
|
||||||
"RoleName": model.RoleDisplayNames[user.Role],
|
"RoleName": model.RoleDisplayNames[user.Role],
|
||||||
"StatusName": model.StatusDisplayNames[user.Status],
|
"StatusName": model.StatusDisplayNames[user.Status],
|
||||||
"PendingTypes": pendingTypes,
|
"PendingTypes": pendingTypes,
|
||||||
"AuditTypes": model.AuditTypeNames,
|
"AuditTypes": model.AuditTypeNames,
|
||||||
|
"HasCompletedRename": hasCompletedRename,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 登录管理 tab 需要额外数据
|
// 登录管理 tab 需要额外数据
|
||||||
|
|||||||
@ -45,18 +45,28 @@ func (s *AuditService) review(reviewerID uint, submissionID uint, approved bool,
|
|||||||
} else {
|
} else {
|
||||||
submission.Status = model.AuditStatusRejected
|
submission.Status = model.AuditStatusRejected
|
||||||
submission.RejectReason = &reason
|
submission.RejectReason = &reason
|
||||||
// 通知用户审核被拒(改名审核追加域能返还提示)
|
|
||||||
|
// 改名审核被拒 → 仅非首次改名(已扣费)才退还域能
|
||||||
|
// 首次改名免费,被拒不退还
|
||||||
|
shouldRefund := false
|
||||||
|
if submission.AuditType == model.AuditTypeUsername && s.energyRefundSvc != nil {
|
||||||
|
if s.levelSvc != nil {
|
||||||
|
hasCompleted, _ := s.levelSvc.HasCompletedTask(submission.UserID, "username")
|
||||||
|
shouldRefund = hasCompleted
|
||||||
|
}
|
||||||
|
if shouldRefund {
|
||||||
|
_ = s.energyRefundSvc.RefundOnRenameReject(submission.UserID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通知用户审核被拒(仅实际退款时追加域能返还提示)
|
||||||
if s.notifier != nil {
|
if s.notifier != nil {
|
||||||
reasonMsg := reason
|
reasonMsg := reason
|
||||||
if submission.AuditType == model.AuditTypeUsername {
|
if shouldRefund {
|
||||||
reasonMsg += "。扣除的域能已被返还"
|
reasonMsg += "。扣除的域能已被返还"
|
||||||
}
|
}
|
||||||
s.notifier.NotifyAuditRejected(submission.UserID, submission.AuditType, reasonMsg, submission.ID)
|
s.notifier.NotifyAuditRejected(submission.UserID, submission.AuditType, reasonMsg, submission.ID)
|
||||||
}
|
}
|
||||||
// 改名审核被拒 → 返还域能
|
|
||||||
if submission.AuditType == model.AuditTypeUsername && s.energyRefundSvc != nil {
|
|
||||||
_ = s.energyRefundSvc.RefundOnRenameReject(submission.UserID)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return s.auditRepo.Update(submission)
|
return s.auditRepo.Update(submission)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -40,6 +40,7 @@ type auditNotifier interface {
|
|||||||
// auditTaskCompleter 审核通过时触发首次任务奖励的接口
|
// auditTaskCompleter 审核通过时触发首次任务奖励的接口
|
||||||
type auditTaskCompleter interface {
|
type auditTaskCompleter interface {
|
||||||
CompleteTask(userID uint, taskType string) (newExp int, levelUp bool, err error)
|
CompleteTask(userID uint, taskType string) (newExp int, levelUp bool, err error)
|
||||||
|
HasCompletedTask(userID uint, taskType string) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// energyRenameRefunder 改名审核被拒时返还域能的接口(ISP)
|
// energyRenameRefunder 改名审核被拒时返还域能的接口(ISP)
|
||||||
|
|||||||
@ -79,7 +79,7 @@
|
|||||||
<div class="info-row">
|
<div class="info-row">
|
||||||
<span class="info-label">用户名</span>
|
<span class="info-label">用户名</span>
|
||||||
<span class="info-value">
|
<span class="info-value">
|
||||||
<input type="text" id="usernameInput" class="settings-text-input" value="{{.User.Username}}" maxlength="16" autocomplete="off">
|
<input type="text" id="usernameInput" class="settings-text-input" value="{{.User.Username}}" maxlength="16" autocomplete="off" data-has-completed-rename="{{.HasCompletedRename}}">
|
||||||
<span class="char-counter" id="usernameCounter"></span>
|
<span class="char-counter" id="usernameCounter"></span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -375,6 +375,7 @@
|
|||||||
// ---- 原始值(用于判断是否变更) ----
|
// ---- 原始值(用于判断是否变更) ----
|
||||||
var origUsername = usernameInput.value.trim();
|
var origUsername = usernameInput.value.trim();
|
||||||
var origBio = bioInput.value;
|
var origBio = bioInput.value;
|
||||||
|
var hasCompletedRename = usernameInput.getAttribute('data-has-completed-rename') === 'true';
|
||||||
|
|
||||||
// 与后端一致的合法字符集:中文、英文大小写、数字、下划线、连字符
|
// 与后端一致的合法字符集:中文、英文大小写、数字、下划线、连字符
|
||||||
var usernameRe = /^[\u4e00-\u9fffa-zA-Z0-9_-]+$/;
|
var usernameRe = /^[\u4e00-\u9fffa-zA-Z0-9_-]+$/;
|
||||||
@ -409,6 +410,21 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 用户名有变更时弹出消耗确认
|
||||||
|
if (username !== origUsername) {
|
||||||
|
var confirmMsg = hasCompletedRename
|
||||||
|
? '确认修改用户名?\n\n本次将消耗 6 域能,是否继续?'
|
||||||
|
: '确认修改用户名?\n\n首次更名无需消耗域能。';
|
||||||
|
showConfirm('修改用户名', confirmMsg).then(function(ok) {
|
||||||
|
if (ok) doSave(username, bio);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
doSave(username, bio);
|
||||||
|
});
|
||||||
|
|
||||||
|
function doSave(username, bio) {
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
btn.textContent = '保存中...';
|
btn.textContent = '保存中...';
|
||||||
|
|
||||||
@ -434,7 +450,7 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
xhr.send(JSON.stringify({ username: username, bio: bio }));
|
xhr.send(JSON.stringify({ username: username, bio: bio }));
|
||||||
});
|
}
|
||||||
|
|
||||||
// ---- 头像裁切上传 ----
|
// ---- 头像裁切上传 ----
|
||||||
var avatarWrap = document.getElementById('avatarWrap');
|
var avatarWrap = document.getElementById('avatarWrap');
|
||||||
|
|||||||
@ -149,8 +149,8 @@ function renderTable(items, bodyId) {
|
|||||||
function renderUsernameRow(item) {
|
function renderUsernameRow(item) {
|
||||||
return `
|
return `
|
||||||
<tr>
|
<tr>
|
||||||
<td>${escapeHtml(item.submitter_username || '—')}</td>
|
<td>${item.user_id || '—'}</td>
|
||||||
<td>—</td>
|
<td>${escapeHtml(item.current_username || '—')}</td>
|
||||||
<td><strong>${escapeHtml(item.new_value)}</strong></td>
|
<td><strong>${escapeHtml(item.new_value)}</strong></td>
|
||||||
<td>${formatTime(item.created_at)}</td>
|
<td>${formatTime(item.created_at)}</td>
|
||||||
<td class="col-actions">
|
<td class="col-actions">
|
||||||
@ -165,7 +165,7 @@ function renderBioRow(item) {
|
|||||||
const display = text.length > 40 ? text.substring(0, 40) + '...' : text;
|
const display = text.length > 40 ? text.substring(0, 40) + '...' : text;
|
||||||
return `
|
return `
|
||||||
<tr>
|
<tr>
|
||||||
<td>${escapeHtml(item.submitter_username || '—')}</td>
|
<td>${item.user_id || '—'}</td>
|
||||||
<td title="${escapeHtml(text)}">${escapeHtml(display)}</td>
|
<td title="${escapeHtml(text)}">${escapeHtml(display)}</td>
|
||||||
<td>${formatTime(item.created_at)}</td>
|
<td>${formatTime(item.created_at)}</td>
|
||||||
<td class="col-actions">
|
<td class="col-actions">
|
||||||
@ -188,7 +188,7 @@ function renderAvatarCards(items) {
|
|||||||
grid.innerHTML = items.map(item => `
|
grid.innerHTML = items.map(item => `
|
||||||
<div class="avatar-audit-card">
|
<div class="avatar-audit-card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span>提交者: <strong>${escapeHtml(item.submitter_username || '—')}</strong></span>
|
<span>提交者: <strong>${item.user_id || '—'}</strong></span>
|
||||||
<span>${formatTime(item.created_at)}</span>
|
<span>${formatTime(item.created_at)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="avatar-compare">
|
<div class="avatar-compare">
|
||||||
@ -224,7 +224,7 @@ function renderHistoryTable(items) {
|
|||||||
return `
|
return `
|
||||||
<tr>
|
<tr>
|
||||||
<td>${typeLabel}</td>
|
<td>${typeLabel}</td>
|
||||||
<td>${escapeHtml(item.submitter_username || '—')}</td>
|
<td>${item.user_id || '—'}</td>
|
||||||
<td>${escapeHtml(content)}</td>
|
<td>${escapeHtml(content)}</td>
|
||||||
<td><span class="audit-status-tag ${statusClass}">${statusLabel}</span></td>
|
<td><span class="audit-status-tag ${statusClass}">${statusLabel}</span></td>
|
||||||
<td>${escapeHtml(reviewer)}</td>
|
<td>${escapeHtml(reviewer)}</td>
|
||||||
|
|||||||
Reference in New Issue
Block a user