Browse Source

refactor(me/profile): toast-only feedback, overlay loading without layout swap

Made-with: Cursor
main
npmrun 6 hours ago
parent
commit
ad5cc6ade5
  1. 43
      app/pages/me/profile/index.vue

43
app/pages/me/profile/index.vue

@ -39,7 +39,6 @@ const loading = ref(true)
const saving = ref(false)
const uploadingAvatar = ref(false)
const uploadingHeaderIcon = ref(false)
const message = ref('')
const avatarFileInput = ref<HTMLInputElement | null>(null)
const headerIconFileInput = ref<HTMLInputElement | null>(null)
@ -86,7 +85,6 @@ async function onAvatarFileChange(ev: Event) {
return
}
uploadingAvatar.value = true
message.value = ''
try {
const form = new FormData()
form.append('file', file)
@ -97,14 +95,13 @@ async function onAvatarFileChange(ev: Event) {
})
const url = files[0]?.url
if (!url) {
message.value = '上传未返回地址'
toast.add({ title: '上传未返回地址', color: 'error' })
return
}
state.avatar = url
message.value = '头像已上传,请点击下方「保存」写入资料'
toast.add({ title: '头像已上传,请保存资料', color: 'success' })
} catch (e: unknown) {
message.value = getApiErrorMessage(e)
toast.add({ title: getApiErrorMessage(e), color: 'error' })
} finally {
uploadingAvatar.value = false
}
@ -118,7 +115,6 @@ async function onHeaderIconFileChange(ev: Event) {
return
}
uploadingHeaderIcon.value = true
message.value = ''
try {
const form = new FormData()
form.append('file', file)
@ -129,14 +125,13 @@ async function onHeaderIconFileChange(ev: Event) {
})
const url = files[0]?.url
if (!url) {
message.value = '上传未返回地址'
toast.add({ title: '上传未返回地址', color: 'error' })
return
}
state.publicHomeHeaderIconUrl = url
message.value = '顶栏图标已上传,请点击下方「保存」写入'
toast.add({ title: '顶栏图标已上传,请保存资料', color: 'success' })
} catch (e: unknown) {
message.value = getApiErrorMessage(e)
toast.add({ title: getApiErrorMessage(e), color: 'error' })
} finally {
uploadingHeaderIcon.value = false
}
@ -170,13 +165,12 @@ onMounted(load)
async function save() {
saving.value = true
message.value = ''
try {
let links: { label: string; url: string; visibility: string }[] = []
try {
links = JSON.parse(state.linksJson) as { label: string; url: string; visibility: string }[]
} catch {
message.value = '社交链接 JSON 无效'
toast.add({ title: '社交链接 JSON 无效', color: 'error' })
return
}
await fetchData('/api/me/profile', {
@ -204,7 +198,6 @@ async function save() {
body: { key: 'publicHomeHeaderIconUrl', value: state.publicHomeHeaderIconUrl },
}),
])
message.value = '已保存'
toast.add({ title: '资料已保存', color: 'success' })
try {
await refreshAuthSession(true)
@ -213,7 +206,7 @@ async function save() {
}
await load()
} catch (e: unknown) {
message.value = getApiErrorMessage(e)
toast.add({ title: getApiErrorMessage(e), color: 'error' })
} finally {
saving.value = false
}
@ -225,10 +218,22 @@ async function save() {
<h1 class="text-2xl font-semibold">
个人资料
</h1>
<div v-if="loading" class="text-muted">
加载中
</div>
<UForm v-else :state="state" class="space-y-4" @submit.prevent="save">
<div class="relative min-h-[28rem]">
<div
v-show="loading"
class="absolute inset-0 z-10 flex flex-col items-center justify-center gap-3 rounded-lg bg-default/75 backdrop-blur-[2px]"
aria-live="polite"
aria-busy="true"
>
<UIcon name="i-lucide-loader-2" class="size-8 animate-spin text-primary" />
<span class="text-sm text-muted">加载中</span>
</div>
<UForm
:state="state"
class="space-y-4"
:class="loading ? 'pointer-events-none select-none opacity-50' : ''"
@submit.prevent="save"
>
<UFormField label="公开主页 slug(/@slug)" name="publicSlug">
<UInput v-model="state.publicSlug" placeholder="例如 my-id" />
</UFormField>
@ -335,11 +340,11 @@ async function save() {
<UFormField label="社交链接(JSON 数组)" name="linksJson">
<UTextarea v-model="state.linksJson" :rows="8" class="w-full font-mono text-sm" />
</UFormField>
<UButton type="submit" :loading="saving">
<UButton type="submit" :loading="saving" :disabled="loading">
保存
</UButton>
</UForm>
<UAlert v-if="message" :title="message" />
</div>
<UModal
v-model:open="bioModalOpen"

Loading…
Cancel
Save