Compare commits

...

6 Commits

Author SHA1 Message Date
Tim
229439aa05 style: enhance BaseUserAvatar presentation 2025-09-24 01:31:31 +08:00
tim
6e4fbc3c42 fix: base avatar 重构 2025-09-24 00:43:57 +08:00
Tim
779264623c Merge pull request #1018 from nagisa77/codex/create-baseuseravatar-component-zv8hyo
feat: add base user avatar component
2025-09-24 00:31:11 +08:00
tim
a1eccb3b1e Revert "feat: add BaseUserAvatar and unify avatar usage"
This reverts commit efbb83924b.
2025-09-24 00:30:23 +08:00
Tim
0f75a95dbe Merge pull request #1017 from nagisa77/codex/create-baseuseravatar-component
feat: unify avatar rendering with BaseUserAvatar
2025-09-24 00:27:10 +08:00
Tim
efbb83924b feat: add BaseUserAvatar and unify avatar usage 2025-09-24 00:26:51 +08:00
3 changed files with 77 additions and 30 deletions

View File

@@ -1,28 +1,19 @@
<template>
<NuxtLink
v-if="isLink"
:to="resolvedLink"
class="base-user-avatar"
:class="wrapperClass"
:style="wrapperStyle"
v-bind="wrapperAttrs"
>
<img :src="currentSrc" :alt="altText" class="base-user-avatar-img" @error="onError" />
<BaseImage :src="currentSrc" :alt="altText" class="base-user-avatar-img" @error="onError" />
</NuxtLink>
<div
v-else
class="base-user-avatar"
:class="wrapperClass"
:style="wrapperStyle"
v-bind="wrapperAttrs"
>
<img :src="currentSrc" :alt="altText" class="base-user-avatar-img" @error="onError" />
</div>
</template>
<script setup>
import { computed, ref, watch } from 'vue'
import { useAttrs } from 'vue'
import BaseImage from './BaseImage.vue'
const DEFAULT_AVATAR = '/default-avatar.svg'
@@ -76,8 +67,6 @@ const resolvedLink = computed(() => {
return null
})
const isLink = computed(() => !props.disableLink && Boolean(resolvedLink.value))
const altText = computed(() => props.alt || '用户头像')
const sizeStyle = computed(() => {
@@ -108,11 +97,33 @@ function onError() {
<style scoped>
.base-user-avatar {
--base-avatar-ring-width: var(--avatar-ring-width, 1.5px);
--base-avatar-ring: var(--avatar-ring, linear-gradient(135deg, #6366f1, #ec4899));
--base-avatar-surface: var(
--avatar-surface,
var(--avatar-placeholder-color, rgba(255, 255, 255, 0.88))
);
--base-avatar-shadow: var(--avatar-shadow, 0 12px 30px -18px rgba(15, 23, 42, 0.55));
display: inline-flex;
align-items: center;
justify-content: center;
overflow: hidden;
background-color: var(--avatar-placeholder-color, #f0f0f0);
position: relative;
box-sizing: border-box;
color: inherit;
border-radius: 50%;
border: var(--base-avatar-ring-width) solid transparent;
background:
var(--base-avatar-surface) padding-box,
var(--base-avatar-ring) border-box;
background-clip: padding-box, border-box;
background-origin: border-box;
box-shadow: var(--base-avatar-shadow);
transition:
transform 0.25s ease,
box-shadow 0.25s ease,
filter 0.25s ease;
}
.base-user-avatar.is-rounded {
@@ -120,7 +131,7 @@ function onError() {
}
.base-user-avatar:not(.is-rounded) {
border-radius: 0;
border-radius: var(--avatar-square-radius, 0);
}
.base-user-avatar-img {
@@ -128,5 +139,51 @@ function onError() {
height: 100%;
object-fit: cover;
display: block;
border-radius: inherit;
position: relative;
z-index: 1;
transition:
transform 0.25s ease,
filter 0.25s ease;
backface-visibility: hidden;
}
.base-user-avatar:hover,
.base-user-avatar:focus-visible {
transform: translateY(-1px) scale(1.02);
box-shadow: 0 18px 35px -20px rgba(15, 23, 42, 0.65);
}
.base-user-avatar:focus-visible {
outline: 2px solid rgba(96, 165, 250, 0.6);
outline-offset: 3px;
}
.base-user-avatar:active {
transform: translateY(0) scale(0.99);
}
.base-user-avatar:hover .base-user-avatar-img,
.base-user-avatar:focus-visible .base-user-avatar-img {
transform: scale(1.03);
filter: saturate(1.08);
}
@media (prefers-reduced-motion: reduce) {
.base-user-avatar,
.base-user-avatar-img {
transition: none;
}
.base-user-avatar:hover,
.base-user-avatar:focus-visible,
.base-user-avatar:active {
transform: none;
}
.base-user-avatar:hover .base-user-avatar-img,
.base-user-avatar:focus-visible .base-user-avatar-img {
transform: none;
}
}
</style>

View File

@@ -1,7 +1,7 @@
<template>
<div class="user-list">
<BasePlaceholder v-if="users.length === 0" text="暂无用户" icon="inbox" />
<div v-for="u in users" :key="u.id" class="user-item" @click="handleUserClick(u)">
<div v-for="u in users" :key="u.id" class="user-item">
<BaseUserAvatar :src="u.avatar" :user-id="u.id" alt="avatar" class="user-avatar" />
<div class="user-info">
<div class="user-name">{{ u.username }}</div>

View File

@@ -85,20 +85,16 @@
</div>
<div class="article-member-avatars-container">
<NuxtLink
v-for="member in article.members"
:key="`${article.id}-${member.id}`"
class="article-member-avatar-item"
:to="`/users/${member.id}`"
>
<div v-for="member in article.members">
<BaseUserAvatar
class="article-member-avatar-item-img"
:src="member.avatar"
:user-id="member.id"
alt="avatar"
:disable-link="true"
:width="25"
/>
</NuxtLink>
</div>
</div>
<div class="article-comments main-info-text">
@@ -634,13 +630,6 @@ watch([selectedCategory, selectedTags], ([newCategory, newTags]) => {
margin-left: 20px;
}
.article-member-avatar-item {
width: 25px;
height: 25px;
border-radius: 50%;
overflow: hidden;
}
.article-member-avatar-item-img {
width: 100%;
height: 100%;
@@ -703,6 +692,7 @@ watch([selectedCategory, selectedTags], ([newCategory, newTags]) => {
margin-left: 0px;
gap: 0px;
}
.article-main-container,
.header-item.main-item {
width: calc(70% - 20px);