mirror of https://github.com/halo-dev/halo-admin
feat: add avatar component
parent
e8cd837a00
commit
8e3578f445
|
@ -1,3 +1,4 @@
|
|||
export * from "./components/avatar";
|
||||
export * from "./components/alert";
|
||||
export * from "./components/button";
|
||||
export * from "./components/card";
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<script lang="ts" setup>
|
||||
import { VAvatar } from "./index";
|
||||
</script>
|
||||
<template>
|
||||
<Story title="Avatar">
|
||||
<VAvatar src="https://ryanc.cc/avatar" size="lg" />
|
||||
</Story>
|
||||
</template>
|
|
@ -0,0 +1,89 @@
|
|||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
import type { Size } from "./interface";
|
||||
import { useImage } from "@vueuse/core";
|
||||
import { IconUserLine } from "@/icons/icons";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
src?: string;
|
||||
alt?: string;
|
||||
size?: Size;
|
||||
width?: string;
|
||||
height?: string;
|
||||
circle?: boolean;
|
||||
}>(),
|
||||
{
|
||||
src: undefined,
|
||||
alt: undefined,
|
||||
size: undefined,
|
||||
width: undefined,
|
||||
height: undefined,
|
||||
circle: false,
|
||||
}
|
||||
);
|
||||
|
||||
const classes = computed(() => {
|
||||
const result = [`avatar-${props.circle ? "circle" : "square"}`];
|
||||
if (props.size) {
|
||||
result.push(`avatar-${props.size}`);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
const styles = computed(() => {
|
||||
const result: Record<string, string> = {};
|
||||
if (props.width) {
|
||||
result.width = props.width;
|
||||
}
|
||||
if (props.height) {
|
||||
result.height = props.height;
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
const { isLoading, error } = useImage({ src: props.src });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="avatar-wrapper" :class="classes" :style="styles">
|
||||
<div v-if="isLoading || error" class="w-full h-full">
|
||||
<IconUserLine class="w-full h-full" />
|
||||
</div>
|
||||
<img v-else :src="src" :alt="alt" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.avatar-wrapper {
|
||||
@apply inline-flex items-center justify-center overflow-hidden bg-white;
|
||||
|
||||
img {
|
||||
@apply w-full h-full object-cover;
|
||||
}
|
||||
|
||||
&.avatar-circle {
|
||||
@apply rounded-full;
|
||||
}
|
||||
|
||||
&.avatar-square {
|
||||
@apply rounded-base;
|
||||
}
|
||||
|
||||
&.avatar-xs {
|
||||
@apply w-6 h-6;
|
||||
}
|
||||
|
||||
&.avatar-sm {
|
||||
@apply w-8 h-8;
|
||||
}
|
||||
|
||||
&.avatar-md {
|
||||
@apply w-10 h-10;
|
||||
}
|
||||
|
||||
&.avatar-lg {
|
||||
@apply w-12 h-12;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1 @@
|
|||
export { default as VAvatar } from "./Avatar.vue";
|
|
@ -0,0 +1 @@
|
|||
export type Size = "lg" | "md" | "sm" | "xs";
|
|
@ -46,6 +46,7 @@ import IconTeam from "~icons/ri/team-fill";
|
|||
import IconCharacterRecognition from "~icons/ri/character-recognition-line";
|
||||
import IconCalendar from "~icons/ri/calendar-line";
|
||||
import IconLink from "~icons/ri/link";
|
||||
import IconUserLine from "~icons/ri/user-line";
|
||||
|
||||
export {
|
||||
IconDashboard,
|
||||
|
@ -96,4 +97,5 @@ export {
|
|||
IconCharacterRecognition,
|
||||
IconCalendar,
|
||||
IconLink,
|
||||
IconUserLine,
|
||||
};
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
VModal,
|
||||
VRoutesMenu,
|
||||
VTag,
|
||||
VAvatar,
|
||||
} from "@halo-dev/components";
|
||||
import type { MenuGroupType, MenuItemType } from "../types/menus";
|
||||
import type { User } from "@halo-dev/api-client";
|
||||
|
@ -59,7 +60,12 @@ const currentRole = computed(() => {
|
|||
<VRoutesMenu :menus="menus" />
|
||||
<div class="current-profile">
|
||||
<div v-if="currentUser?.spec.avatar" class="profile-avatar">
|
||||
<img :src="currentUser?.spec.avatar" class="h-11 w-11 rounded-full" />
|
||||
<VAvatar
|
||||
:src="currentUser?.spec.avatar"
|
||||
:alt="currentUser?.spec.displayName"
|
||||
size="md"
|
||||
circle
|
||||
></VAvatar>
|
||||
</div>
|
||||
<div class="profile-name">
|
||||
<div class="flex text-sm font-medium">
|
||||
|
@ -204,7 +210,8 @@ const currentRole = computed(() => {
|
|||
|
||||
.profile-avatar {
|
||||
@apply self-center
|
||||
flex;
|
||||
flex
|
||||
items-center;
|
||||
}
|
||||
|
||||
.profile-name {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts" setup>
|
||||
import type { User } from "@halo-dev/api-client";
|
||||
import { useUserFetch } from "@/modules/system/users/composables/use-user";
|
||||
import { IconCheckboxCircle } from "@halo-dev/components";
|
||||
import { IconCheckboxCircle, VAvatar } from "@halo-dev/components";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
|
@ -57,12 +57,12 @@ function onDropdownShow() {
|
|||
@click="handleSelect(user)"
|
||||
>
|
||||
<div class="flex items-center space-x-4 px-4 py-3">
|
||||
<div class="flex-shrink-0">
|
||||
<img
|
||||
<div class="flex flex-shrink-0 items-center">
|
||||
<VAvatar
|
||||
:alt="user.spec.displayName"
|
||||
:src="user.spec.avatar"
|
||||
class="h-10 w-10 rounded"
|
||||
/>
|
||||
size="md"
|
||||
></VAvatar>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="truncate text-sm font-medium text-gray-900">
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
VPageHeader,
|
||||
VPagination,
|
||||
VSpace,
|
||||
VAvatar,
|
||||
} from "@halo-dev/components";
|
||||
import { ref } from "vue";
|
||||
|
||||
|
@ -117,12 +118,13 @@ const checkAll = ref(false);
|
|||
</div>
|
||||
<div class="flex">
|
||||
<div
|
||||
class="inline-flex flex-col flex-col-reverse items-end gap-4 sm:flex-row sm:items-center sm:gap-6"
|
||||
class="inline-flex flex-col items-end gap-4 sm:flex-row sm:items-center sm:gap-6"
|
||||
>
|
||||
<img
|
||||
class="hidden h-6 w-6 rounded-full ring-2 ring-white sm:inline-block"
|
||||
<VAvatar
|
||||
size="xs"
|
||||
src="https://ryanc.cc/avatar"
|
||||
/>
|
||||
circle
|
||||
></VAvatar>
|
||||
<time class="text-sm text-gray-500" datetime="2020-01-07">
|
||||
2020-01-07
|
||||
</time>
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
VSpace,
|
||||
useDialog,
|
||||
VEmpty,
|
||||
VAvatar,
|
||||
} from "@halo-dev/components";
|
||||
import SinglePageSettingModal from "./components/SinglePageSettingModal.vue";
|
||||
import UserDropdownSelector from "@/components/dropdown-selector/UserDropdownSelector.vue";
|
||||
|
@ -402,14 +403,15 @@ const handleSelectUser = (user?: User) => {
|
|||
name: 'UserDetail',
|
||||
params: { name: contributor.name },
|
||||
}"
|
||||
class="flex items-center"
|
||||
>
|
||||
<img
|
||||
<VAvatar
|
||||
v-tooltip="contributor.displayName"
|
||||
:alt="contributor.name"
|
||||
size="xs"
|
||||
:src="contributor.avatar"
|
||||
:title="contributor.displayName"
|
||||
class="hidden h-6 w-6 rounded-full ring-2 ring-white sm:inline-block"
|
||||
/>
|
||||
:alt="contributor.displayName"
|
||||
circle
|
||||
></VAvatar>
|
||||
</RouterLink>
|
||||
<span class="text-sm text-gray-500">
|
||||
{{ finalStatus(singlePage.page) }}
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
VPageHeader,
|
||||
VPagination,
|
||||
VSpace,
|
||||
VAvatar,
|
||||
} from "@halo-dev/components";
|
||||
import UserDropdownSelector from "@/components/dropdown-selector/UserDropdownSelector.vue";
|
||||
import PostSettingModal from "./components/PostSettingModal.vue";
|
||||
|
@ -833,14 +834,15 @@ function handleContributorFilterItemChange(user?: User) {
|
|||
name: 'UserDetail',
|
||||
params: { name: contributor.name },
|
||||
}"
|
||||
class="flex items-center"
|
||||
>
|
||||
<img
|
||||
<VAvatar
|
||||
v-tooltip="contributor.displayName"
|
||||
:alt="contributor.name"
|
||||
size="xs"
|
||||
:src="contributor.avatar"
|
||||
:title="contributor.displayName"
|
||||
class="hidden h-6 w-6 rounded-full ring-2 ring-white sm:inline-block"
|
||||
/>
|
||||
:alt="contributor.displayName"
|
||||
circle
|
||||
></VAvatar>
|
||||
</RouterLink>
|
||||
<span class="text-sm text-gray-500">
|
||||
{{ finalStatus(post.post) }}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts" name="RecentLoginWidget" setup>
|
||||
import { VCard } from "@halo-dev/components";
|
||||
import { VCard, VAvatar } from "@halo-dev/components";
|
||||
import { useUserFetch } from "@/modules/system/users/composables/use-user";
|
||||
|
||||
const { users } = useUserFetch({ fetchOnMounted: true });
|
||||
|
@ -18,11 +18,11 @@ const { users } = useUserFetch({ fetchOnMounted: true });
|
|||
class="cursor-pointer py-4 hover:bg-gray-50"
|
||||
>
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="flex-shrink-0">
|
||||
<img
|
||||
<div class="flex flex-shrink-0 items-center">
|
||||
<VAvatar
|
||||
:alt="user.spec.displayName"
|
||||
:src="user.spec.avatar"
|
||||
class="h-10 w-10 rounded"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
VPageHeader,
|
||||
VTabbar,
|
||||
VTag,
|
||||
VAvatar,
|
||||
} from "@halo-dev/components";
|
||||
import { useRoute } from "vue-router";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
|
@ -175,19 +176,13 @@ onMounted(() => {
|
|||
<li class="block cursor-pointer hover:bg-gray-50">
|
||||
<div class="flex items-center px-4 py-4">
|
||||
<div class="flex min-w-0 flex-1 items-center">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="h-12 w-12">
|
||||
<div
|
||||
class="overflow-hidden rounded border bg-white hover:shadow-sm"
|
||||
>
|
||||
<img
|
||||
<div class="flex flex-shrink-0 items-center">
|
||||
<VAvatar
|
||||
:alt="user.spec.displayName"
|
||||
:src="user.spec.avatar"
|
||||
class="h-full w-full"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="min-w-0 flex-1 px-4 md:grid md:grid-cols-2 md:gap-4"
|
||||
>
|
||||
|
|
|
@ -240,7 +240,7 @@ const handleDelete = async (role: Role) => {
|
|||
</div>
|
||||
<div class="flex">
|
||||
<div
|
||||
class="inline-flex flex-col flex-col-reverse items-end gap-4 sm:flex-row sm:items-center sm:gap-6"
|
||||
class="inline-flex flex-col items-end gap-4 sm:flex-row sm:items-center sm:gap-6"
|
||||
>
|
||||
<FloatingTooltip
|
||||
v-if="role.metadata.deletionTimestamp"
|
||||
|
|
|
@ -267,7 +267,7 @@ onMounted(() => {
|
|||
</div>
|
||||
<div class="flex">
|
||||
<div
|
||||
class="inline-flex flex-col flex-col-reverse items-end gap-4 sm:flex-row sm:items-center sm:gap-6"
|
||||
class="inline-flex flex-col items-end gap-4 sm:flex-row sm:items-center sm:gap-6"
|
||||
>
|
||||
<div class="flex flex-col gap-1">
|
||||
<time class="text-xs text-gray-500" datetime="2020-01-07">
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
VPagination,
|
||||
VSpace,
|
||||
VTag,
|
||||
VAvatar,
|
||||
} from "@halo-dev/components";
|
||||
import UserEditingModal from "./components/UserEditingModal.vue";
|
||||
import UserPasswordChangeModal from "./components/UserPasswordChangeModal.vue";
|
||||
|
@ -293,14 +294,12 @@ onMounted(() => {
|
|||
type="checkbox"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="user.spec.avatar" class="mr-4">
|
||||
<div class="h-12 w-12">
|
||||
<img
|
||||
<div v-if="user.spec.avatar" class="mr-4 flex items-center">
|
||||
<VAvatar
|
||||
:alt="user.spec.displayName"
|
||||
:src="user.spec.avatar"
|
||||
class="h-full w-full overflow-hidden rounded border bg-white hover:shadow-sm"
|
||||
/>
|
||||
</div>
|
||||
size="md"
|
||||
></VAvatar>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="flex flex-row items-center">
|
||||
|
|
Loading…
Reference in New Issue