feat: 取消显示名称,统一用户名,大小写不敏感去重,一次性改名
All checks were successful
PR Preview / teardown-preview (pull_request) Has been skipped
Test / unit-test (push) Successful in 5s
Test / build-check (push) Successful in 3s
PR Preview / test (pull_request) Successful in 5s
PR Preview / deploy-preview (pull_request) Successful in 16s
Test / e2e-test (push) Successful in 52s

- 注册: 去掉display_name字段,用户名大小写不敏感去重
- 登录: 大小写不敏感匹配
- 用户菜单: 显示用户名+改名按钮(只能改一次)
- /api/me/username: 一次性改名API
- 启动时: sync display_name=username, 发通知告知用户
- 前端: 所有display_name显示改为username

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-12 22:20:23 +00:00
parent ca3f409827
commit fdc7b20929
6 changed files with 89 additions and 26 deletions

View File

@@ -15,7 +15,7 @@
<div class="header-right" @click="toggleUserMenu">
<template v-if="auth.isLoggedIn">
<span v-if="auth.isBusiness" class="biz-badge" title="商业认证用户">🏢</span>
<span class="user-name">{{ auth.user.display_name || auth.user.username }} </span>
<span class="user-name">{{ auth.user.username }} </span>
<span v-if="unreadNotifCount > 0" class="notif-badge">{{ unreadNotifCount }}</span>
</template>
<template v-else>

View File

@@ -37,14 +37,6 @@
class="login-input"
@keydown.enter="submit"
/>
<input
v-if="mode === 'register'"
v-model="displayName"
type="text"
placeholder="显示名称(可选)"
class="login-input"
@keydown.enter="submit"
/>
<div v-if="errorMsg" class="login-error">{{ errorMsg }}</div>
@@ -80,7 +72,6 @@ const mode = ref('login')
const username = ref('')
const password = ref('')
const confirmPassword = ref('')
const displayName = ref('')
const errorMsg = ref('')
const loading = ref(false)
const showFeedback = ref(false)
@@ -109,11 +100,7 @@ async function submit() {
await auth.login(username.value.trim(), password.value)
ui.showToast('登录成功')
} else {
await auth.register(
username.value.trim(),
password.value,
displayName.value.trim() || username.value.trim()
)
await auth.register(username.value.trim(), password.value)
ui.showToast('注册成功')
}
emit('close')

View File

@@ -1,7 +1,10 @@
<template>
<div class="usermenu-overlay" @mousedown.self="$emit('close')">
<div class="usermenu-card">
<div class="usermenu-name">{{ auth.user.display_name || auth.user.username }}</div>
<div class="usermenu-name">
{{ auth.user.username }}
<button v-if="!auth.user.username_changed" class="rename-btn" @click="changeUsername"> 改名</button>
</div>
<div class="usermenu-actions">
<button class="usermenu-btn" @click="goMyDiary">
@@ -131,6 +134,27 @@ function isSearchMissing(n) {
return n.title && n.title.includes('用户需求')
}
async function changeUsername() {
const { showPrompt } = await import('../composables/useDialog')
const newName = await showPrompt('输入新用户名(只能修改一次):', auth.user.username)
if (!newName || !newName.trim() || newName.trim() === auth.user.username) return
try {
const res = await api('/api/me/username', {
method: 'PUT',
body: JSON.stringify({ username: newName.trim() }),
})
if (res.ok) {
await auth.loadMe()
ui.showToast('用户名已修改')
} else {
const err = await res.json().catch(() => ({}))
ui.showToast(err.detail || '修改失败')
}
} catch {
ui.showToast('修改失败')
}
}
function isPlanRequest(n) {
return n.title && (n.title.includes('方案请求') || n.title.includes('方案需求'))
}
@@ -227,7 +251,9 @@ onMounted(loadNotifications)
z-index: 4001;
}
.usermenu-name { font-size: 16px; font-weight: 600; color: #3e3a44; margin-bottom: 14px; }
.usermenu-name { font-size: 16px; font-weight: 600; color: #3e3a44; margin-bottom: 14px; display: flex; align-items: center; gap: 8px; }
.rename-btn { background: none; border: none; font-size: 12px; color: #b0aab5; cursor: pointer; padding: 0; }
.rename-btn:hover { color: #4a9d7e; }
.usermenu-actions { display: flex; flex-direction: column; gap: 4px; }
.usermenu-btn {

View File

@@ -40,9 +40,10 @@ export const useAuthStore = defineStore('auth', () => {
id: data.id,
role: data.role,
username: data.username,
display_name: data.display_name,
display_name: data.username,
has_password: data.has_password ?? false,
business_verified: data.business_verified ?? false,
username_changed: data.username_changed ?? false,
}
} catch {
logout()
@@ -56,11 +57,10 @@ export const useAuthStore = defineStore('auth', () => {
await loadMe()
}
async function register(username, password, displayName) {
async function register(username, password) {
const data = await api.post('/api/register', {
username,
password,
display_name: displayName,
})
token.value = data.token
localStorage.setItem('oil_auth_token', data.token)