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/alert";
|
||||||
export * from "./components/button";
|
export * from "./components/button";
|
||||||
export * from "./components/card";
|
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 IconCharacterRecognition from "~icons/ri/character-recognition-line";
|
||||||
import IconCalendar from "~icons/ri/calendar-line";
|
import IconCalendar from "~icons/ri/calendar-line";
|
||||||
import IconLink from "~icons/ri/link";
|
import IconLink from "~icons/ri/link";
|
||||||
|
import IconUserLine from "~icons/ri/user-line";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
IconDashboard,
|
IconDashboard,
|
||||||
|
@ -96,4 +97,5 @@ export {
|
||||||
IconCharacterRecognition,
|
IconCharacterRecognition,
|
||||||
IconCalendar,
|
IconCalendar,
|
||||||
IconLink,
|
IconLink,
|
||||||
|
IconUserLine,
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
VModal,
|
VModal,
|
||||||
VRoutesMenu,
|
VRoutesMenu,
|
||||||
VTag,
|
VTag,
|
||||||
|
VAvatar,
|
||||||
} from "@halo-dev/components";
|
} from "@halo-dev/components";
|
||||||
import type { MenuGroupType, MenuItemType } from "../types/menus";
|
import type { MenuGroupType, MenuItemType } from "../types/menus";
|
||||||
import type { User } from "@halo-dev/api-client";
|
import type { User } from "@halo-dev/api-client";
|
||||||
|
@ -59,7 +60,12 @@ const currentRole = computed(() => {
|
||||||
<VRoutesMenu :menus="menus" />
|
<VRoutesMenu :menus="menus" />
|
||||||
<div class="current-profile">
|
<div class="current-profile">
|
||||||
<div v-if="currentUser?.spec.avatar" class="profile-avatar">
|
<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>
|
||||||
<div class="profile-name">
|
<div class="profile-name">
|
||||||
<div class="flex text-sm font-medium">
|
<div class="flex text-sm font-medium">
|
||||||
|
@ -204,7 +210,8 @@ const currentRole = computed(() => {
|
||||||
|
|
||||||
.profile-avatar {
|
.profile-avatar {
|
||||||
@apply self-center
|
@apply self-center
|
||||||
flex;
|
flex
|
||||||
|
items-center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-name {
|
.profile-name {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { User } from "@halo-dev/api-client";
|
import type { User } from "@halo-dev/api-client";
|
||||||
import { useUserFetch } from "@/modules/system/users/composables/use-user";
|
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(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
@ -57,12 +57,12 @@ function onDropdownShow() {
|
||||||
@click="handleSelect(user)"
|
@click="handleSelect(user)"
|
||||||
>
|
>
|
||||||
<div class="flex items-center space-x-4 px-4 py-3">
|
<div class="flex items-center space-x-4 px-4 py-3">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex flex-shrink-0 items-center">
|
||||||
<img
|
<VAvatar
|
||||||
:alt="user.spec.displayName"
|
:alt="user.spec.displayName"
|
||||||
:src="user.spec.avatar"
|
:src="user.spec.avatar"
|
||||||
class="h-10 w-10 rounded"
|
size="md"
|
||||||
/>
|
></VAvatar>
|
||||||
</div>
|
</div>
|
||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
<p class="truncate text-sm font-medium text-gray-900">
|
<p class="truncate text-sm font-medium text-gray-900">
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
VPageHeader,
|
VPageHeader,
|
||||||
VPagination,
|
VPagination,
|
||||||
VSpace,
|
VSpace,
|
||||||
|
VAvatar,
|
||||||
} from "@halo-dev/components";
|
} from "@halo-dev/components";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
@ -117,12 +118,13 @@ const checkAll = ref(false);
|
||||||
</div>
|
</div>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div
|
<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
|
<VAvatar
|
||||||
class="hidden h-6 w-6 rounded-full ring-2 ring-white sm:inline-block"
|
size="xs"
|
||||||
src="https://ryanc.cc/avatar"
|
src="https://ryanc.cc/avatar"
|
||||||
/>
|
circle
|
||||||
|
></VAvatar>
|
||||||
<time class="text-sm text-gray-500" datetime="2020-01-07">
|
<time class="text-sm text-gray-500" datetime="2020-01-07">
|
||||||
2020-01-07
|
2020-01-07
|
||||||
</time>
|
</time>
|
||||||
|
|
|
@ -15,6 +15,7 @@ import {
|
||||||
VSpace,
|
VSpace,
|
||||||
useDialog,
|
useDialog,
|
||||||
VEmpty,
|
VEmpty,
|
||||||
|
VAvatar,
|
||||||
} from "@halo-dev/components";
|
} from "@halo-dev/components";
|
||||||
import SinglePageSettingModal from "./components/SinglePageSettingModal.vue";
|
import SinglePageSettingModal from "./components/SinglePageSettingModal.vue";
|
||||||
import UserDropdownSelector from "@/components/dropdown-selector/UserDropdownSelector.vue";
|
import UserDropdownSelector from "@/components/dropdown-selector/UserDropdownSelector.vue";
|
||||||
|
@ -402,14 +403,15 @@ const handleSelectUser = (user?: User) => {
|
||||||
name: 'UserDetail',
|
name: 'UserDetail',
|
||||||
params: { name: contributor.name },
|
params: { name: contributor.name },
|
||||||
}"
|
}"
|
||||||
|
class="flex items-center"
|
||||||
>
|
>
|
||||||
<img
|
<VAvatar
|
||||||
v-tooltip="contributor.displayName"
|
v-tooltip="contributor.displayName"
|
||||||
:alt="contributor.name"
|
size="xs"
|
||||||
:src="contributor.avatar"
|
:src="contributor.avatar"
|
||||||
:title="contributor.displayName"
|
:alt="contributor.displayName"
|
||||||
class="hidden h-6 w-6 rounded-full ring-2 ring-white sm:inline-block"
|
circle
|
||||||
/>
|
></VAvatar>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<span class="text-sm text-gray-500">
|
<span class="text-sm text-gray-500">
|
||||||
{{ finalStatus(singlePage.page) }}
|
{{ finalStatus(singlePage.page) }}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {
|
||||||
VPageHeader,
|
VPageHeader,
|
||||||
VPagination,
|
VPagination,
|
||||||
VSpace,
|
VSpace,
|
||||||
|
VAvatar,
|
||||||
} from "@halo-dev/components";
|
} from "@halo-dev/components";
|
||||||
import UserDropdownSelector from "@/components/dropdown-selector/UserDropdownSelector.vue";
|
import UserDropdownSelector from "@/components/dropdown-selector/UserDropdownSelector.vue";
|
||||||
import PostSettingModal from "./components/PostSettingModal.vue";
|
import PostSettingModal from "./components/PostSettingModal.vue";
|
||||||
|
@ -833,14 +834,15 @@ function handleContributorFilterItemChange(user?: User) {
|
||||||
name: 'UserDetail',
|
name: 'UserDetail',
|
||||||
params: { name: contributor.name },
|
params: { name: contributor.name },
|
||||||
}"
|
}"
|
||||||
|
class="flex items-center"
|
||||||
>
|
>
|
||||||
<img
|
<VAvatar
|
||||||
v-tooltip="contributor.displayName"
|
v-tooltip="contributor.displayName"
|
||||||
:alt="contributor.name"
|
size="xs"
|
||||||
:src="contributor.avatar"
|
:src="contributor.avatar"
|
||||||
:title="contributor.displayName"
|
:alt="contributor.displayName"
|
||||||
class="hidden h-6 w-6 rounded-full ring-2 ring-white sm:inline-block"
|
circle
|
||||||
/>
|
></VAvatar>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<span class="text-sm text-gray-500">
|
<span class="text-sm text-gray-500">
|
||||||
{{ finalStatus(post.post) }}
|
{{ finalStatus(post.post) }}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts" name="RecentLoginWidget" setup>
|
<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";
|
import { useUserFetch } from "@/modules/system/users/composables/use-user";
|
||||||
|
|
||||||
const { users } = useUserFetch({ fetchOnMounted: true });
|
const { users } = useUserFetch({ fetchOnMounted: true });
|
||||||
|
@ -18,11 +18,11 @@ const { users } = useUserFetch({ fetchOnMounted: true });
|
||||||
class="cursor-pointer py-4 hover:bg-gray-50"
|
class="cursor-pointer py-4 hover:bg-gray-50"
|
||||||
>
|
>
|
||||||
<div class="flex items-center space-x-4">
|
<div class="flex items-center space-x-4">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex flex-shrink-0 items-center">
|
||||||
<img
|
<VAvatar
|
||||||
:alt="user.spec.displayName"
|
:alt="user.spec.displayName"
|
||||||
:src="user.spec.avatar"
|
:src="user.spec.avatar"
|
||||||
class="h-10 w-10 rounded"
|
size="md"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
VPageHeader,
|
VPageHeader,
|
||||||
VTabbar,
|
VTabbar,
|
||||||
VTag,
|
VTag,
|
||||||
|
VAvatar,
|
||||||
} from "@halo-dev/components";
|
} from "@halo-dev/components";
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
import { onMounted, ref, watch } from "vue";
|
import { onMounted, ref, watch } from "vue";
|
||||||
|
@ -175,19 +176,13 @@ onMounted(() => {
|
||||||
<li class="block cursor-pointer hover:bg-gray-50">
|
<li class="block cursor-pointer hover:bg-gray-50">
|
||||||
<div class="flex items-center px-4 py-4">
|
<div class="flex items-center px-4 py-4">
|
||||||
<div class="flex min-w-0 flex-1 items-center">
|
<div class="flex min-w-0 flex-1 items-center">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex flex-shrink-0 items-center">
|
||||||
<div class="h-12 w-12">
|
<VAvatar
|
||||||
<div
|
|
||||||
class="overflow-hidden rounded border bg-white hover:shadow-sm"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
:alt="user.spec.displayName"
|
:alt="user.spec.displayName"
|
||||||
:src="user.spec.avatar"
|
:src="user.spec.avatar"
|
||||||
class="h-full w-full"
|
size="md"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
class="min-w-0 flex-1 px-4 md:grid md:grid-cols-2 md:gap-4"
|
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>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div
|
<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
|
<FloatingTooltip
|
||||||
v-if="role.metadata.deletionTimestamp"
|
v-if="role.metadata.deletionTimestamp"
|
||||||
|
|
|
@ -267,7 +267,7 @@ onMounted(() => {
|
||||||
</div>
|
</div>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div
|
<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">
|
<div class="flex flex-col gap-1">
|
||||||
<time class="text-xs text-gray-500" datetime="2020-01-07">
|
<time class="text-xs text-gray-500" datetime="2020-01-07">
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
VPagination,
|
VPagination,
|
||||||
VSpace,
|
VSpace,
|
||||||
VTag,
|
VTag,
|
||||||
|
VAvatar,
|
||||||
} from "@halo-dev/components";
|
} from "@halo-dev/components";
|
||||||
import UserEditingModal from "./components/UserEditingModal.vue";
|
import UserEditingModal from "./components/UserEditingModal.vue";
|
||||||
import UserPasswordChangeModal from "./components/UserPasswordChangeModal.vue";
|
import UserPasswordChangeModal from "./components/UserPasswordChangeModal.vue";
|
||||||
|
@ -293,14 +294,12 @@ onMounted(() => {
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="user.spec.avatar" class="mr-4">
|
<div v-if="user.spec.avatar" class="mr-4 flex items-center">
|
||||||
<div class="h-12 w-12">
|
<VAvatar
|
||||||
<img
|
|
||||||
:alt="user.spec.displayName"
|
:alt="user.spec.displayName"
|
||||||
:src="user.spec.avatar"
|
:src="user.spec.avatar"
|
||||||
class="h-full w-full overflow-hidden rounded border bg-white hover:shadow-sm"
|
size="md"
|
||||||
/>
|
></VAvatar>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="flex flex-row items-center">
|
<div class="flex flex-row items-center">
|
||||||
|
|
Loading…
Reference in New Issue