feat(component): 新增默认头像 (#104)

This commit is contained in:
Dawn
2024-12-16 02:54:15 +08:00
committed by GitHub
parent 491860a85c
commit 5a4ff7065b
61 changed files with 147 additions and 372 deletions

1
.gitignore vendored
View File

@@ -19,6 +19,7 @@ dist-ssr
*.njsproj
*.sln
*.sw?
.idea
src-tauri/target

8
.idea/.gitignore generated vendored
View File

@@ -1,8 +0,0 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

12
.idea/HuLa.iml generated
View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src-tauri/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/src-tauri/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="ts-external-references" level="project" />
</component>
</module>

6
.idea/bun.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="BunSettings">
<option name="bunPath" value="$USER_HOME$/.bun/bin/bun" />
</component>
</project>

View File

@@ -1,62 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<option name="LINE_SEPARATOR" value="&#10;" />
<HTMLCodeStyleSettings>
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
</HTMLCodeStyleSettings>
<JSCodeStyleSettings version="0">
<option name="USE_SEMICOLON_AFTER_STATEMENT" value="false" />
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="USE_DOUBLE_QUOTES" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
</JSCodeStyleSettings>
<TypeScriptCodeStyleSettings version="0">
<option name="USE_SEMICOLON_AFTER_STATEMENT" value="false" />
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="USE_DOUBLE_QUOTES" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
</TypeScriptCodeStyleSettings>
<VueCodeStyleSettings>
<option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
<option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
</VueCodeStyleSettings>
<codeStyleSettings language="HTML">
<option name="SOFT_MARGINS" value="120" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JavaScript">
<option name="SOFT_MARGINS" value="120" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="TypeScript">
<option name="SOFT_MARGINS" value="120" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Vue">
<option name="SOFT_MARGINS" value="120" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>

View File

@@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GitToolBoxBlameSettings">
<option name="version" value="2" />
</component>
</project>

5
.idea/icon.svg generated

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 136 KiB

View File

@@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<file url="PROJECT" libraries="{ts-external-references}" />
</component>
</project>

View File

@@ -1,14 +0,0 @@
<component name="libraryTable">
<library name="ts-external-references" type="javaScript">
<properties>
<sourceFilesUrls>
<item url="file://$PROJECT_DIR$/node_modules/.pnpm/@rspack+core@1.1.0_@swc+helpers@0.5.15/node_modules/@rspack/core/module.d.ts" />
</sourceFilesUrls>
</properties>
<CLASSES>
<root url="file://$PROJECT_DIR$/node_modules/.pnpm/@rspack+core@1.1.0_@swc+helpers@0.5.15/node_modules/@rspack/core/module.d.ts" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

8
.idea/modules.xml generated
View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/HuLa.iml" filepath="$PROJECT_DIR$/.idea/HuLa.iml" />
</modules>
</component>
</project>

6
.idea/prettier.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PrettierConfiguration">
<option name="myConfigurationMode" value="AUTOMATIC" />
</component>
</project>

6
.idea/vcs.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

2
.npmrc
View File

@@ -1,2 +1,4 @@
# 配置npm镜像源 (华为云)
registry=https://repo.huaweicloud.com/repository/npm/
# 严格检查 package.json 中 "engines" 字段指定的版本要求
engine-strict=true

View File

@@ -1,18 +0,0 @@
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.16.3
hooks:
- id: gitleaks
- repo: https://github.com/jumanjihouse/pre-commit-hooks
rev: 3.0.0
hooks:
- id: shellcheck
- repo: https://github.com/pre-commit/mirrors-eslint
rev: v8.38.0
hooks:
- id: eslint
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace

BIN
public/avatar/001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

BIN
public/avatar/002.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

BIN
public/avatar/003.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

BIN
public/avatar/004.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

BIN
public/avatar/005.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

BIN
public/avatar/006.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

BIN
public/avatar/007.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

BIN
public/avatar/008.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

BIN
public/avatar/009.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

BIN
public/avatar/010.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

BIN
public/avatar/011.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

BIN
public/avatar/012.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

BIN
public/avatar/013.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

BIN
public/avatar/014.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

BIN
public/avatar/015.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

BIN
public/avatar/016.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

BIN
public/avatar/017.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

BIN
public/avatar/018.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

BIN
public/avatar/019.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

BIN
public/avatar/020.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

BIN
public/avatar/021.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 601 B

View File

@@ -28,10 +28,7 @@
<n-flex vertical justify="center" :size="20" class="p-20px">
<n-flex align="center" justify="center" :size="20">
<n-avatar v-if="userInfo.avatar" round size="large" :src="userInfo.avatar" />
<n-avatar v-else round size="large" :src="userInfo.avatar">
{{ userInfo.name?.slice(0, 1) }}
</n-avatar>
<n-avatar round size="large" :src="avatarSrc" />
<n-flex vertical :size="10">
<p class="text-[--text-color]">{{ userInfo.name }}</p>
@@ -64,9 +61,11 @@ import { useUserInfo } from '@/hooks/useCached.ts'
import apis from '@/services/apis.ts'
import { useCommon } from '@/hooks/useCommon.ts'
import { useUserStore } from '@/stores/user.ts'
import { AvatarUtils } from '@/utils/avatarUtils'
const globalStore = useGlobalStore()
const userStore = useUserStore()
const avatarSrc = computed(() => AvatarUtils.getAvatarUrl(userStore.userInfo.avatar as string))
const { countGraphemes } = useCommon()
const userInfo = ref(useUserInfo(globalStore.addFriendModalInfo.uid).value)
const requestMsg = ref()

View File

@@ -3,24 +3,7 @@
<n-flex vertical :size="26" class="size-fit box-border rounded-8px relative min-h-[300px]">
<n-flex vertical :size="20" class="size-full p-10px box-border z-10">
<n-flex vertical :size="20" align="center">
<n-avatar
v-if="isCurrentUser.avatar"
:bordered="true"
round
:size="80"
:src="isCurrentUser.avatar"
fallback-src="/logo.png"></n-avatar>
<n-avatar
v-else
:bordered="true"
round
:color="'#909090'"
:size="80"
:src="isCurrentUser.avatar"
fallback-src="/logo.png">
{{ isCurrentUser.name }}
</n-avatar>
<n-avatar :bordered="true" round :size="80" :src="avatarSrc" fallback-src="/logo.png" />
<n-flex :size="5" align="center" style="margin-left: -4px" class="item-hover">
<img class="rounded-50% w-18px h-18px" src="/status/weather_3x.png" alt="" />
@@ -64,20 +47,22 @@
</n-flex>
<!-- 背景 -->
<img
<n-avatar
:bordered="true"
class="size-full rounded-8px box-border p-20px absolute top-0 left-0 blur-xl opacity-80"
:src="isCurrentUser.avatar"
alt="" />
:src="avatarSrc" />
</n-flex>
</template>
<script setup lang="ts">
import { useBadgeInfo, useUserInfo } from '@/hooks/useCached.ts'
import { AvatarUtils } from '@/utils/avatarUtils'
const { uid } = defineProps<{
uid: number
}>()
const isCurrentUser = computed(() => useUserInfo(uid).value)
const avatarSrc = computed(() => AvatarUtils.getAvatarUrl(useUserInfo(uid).value.avatar as string))
</script>
<style scoped lang="scss">

View File

@@ -16,7 +16,7 @@
justify="space-between"
class="bg-[--center-bg-color] rounded-10px p-20px box-border border-(1px solid [--bg-popover])">
<n-flex align="center" :size="10">
<n-avatar round size="large" :src="useUserInfo(item.uid).value.avatar" class="mr-10px" />
<n-avatar round size="large" :src="avatarSrc(useUserInfo(item.uid).value.avatar)" class="mr-10px" />
<n-flex vertical :size="12">
<n-flex align="center" :size="10">
<n-popover
@@ -54,9 +54,12 @@
import { useContactStore } from '@/stores/contacts.ts'
import { useUserInfo } from '@/hooks/useCached.ts'
import { RequestFriendAgreeStatus } from '@/services/types.ts'
import { AvatarUtils } from '@/utils/avatarUtils.ts'
const contactStore = useContactStore()
const currentUserId = ref(0)
const avatarSrc = (url: string) => AvatarUtils.getAvatarUrl(url)
</script>
<style scoped lang="scss"></style>

View File

@@ -1,15 +1,7 @@
<template>
<!-- 好友详情 -->
<n-flex v-if="content.type === RoomTypeEnum.SINGLE" vertical align="center" :size="30" class="mt-60px select-none">
<n-avatar v-if="item.avatar" class="rounded-50% size-146px border-(2px solid #fff)" :src="item.avatar" />
<n-avatar
v-else
:color="'#909090'"
class="rounded-50% size-146px text-28px border-(2px solid #fff)"
:src="item.avatar">
{{ item.name!.slice(0, 1) }}
</n-avatar>
<n-avatar class="rounded-50% size-146px border-(2px solid #fff)" :src="AvatarUtils.getAvatarUrl(item.avatar)" />
<span class="text-(20px [--text-color])">{{ item.name }}</span>
@@ -96,7 +88,7 @@
<template #avatar="{ option: { name, src } }">
<n-tooltip>
<template #trigger>
<n-avatar :src="src" />
<n-avatar :src="AvatarUtils.getAvatarUrl(src)" />
</template>
{{ name }}
</n-tooltip>
@@ -115,6 +107,7 @@ import { RoomTypeEnum } from '@/enums'
import { lightTheme } from 'naive-ui'
import { useBadgeInfo, useUserInfo } from '@/hooks/useCached.ts'
import { useCommon } from '@/hooks/useCommon.ts'
import { AvatarUtils } from '@/utils/avatarUtils'
const { openMsgSession } = useCommon()
const { content } = defineProps<{

View File

@@ -42,31 +42,15 @@
align="center"
class="ait-item">
<n-avatar
v-if="item.avatar"
lazy
round
:size="22"
:src="item.avatar"
:src="AvatarUtils.getAvatarUrl(item.avatar)"
fallback-src="/logo.png"
:render-placeholder="() => null"
:intersection-observer-options="{
root: '#image-chat-msgInput'
}" />
<n-avatar
v-else
lazy
round
:color="'#909090'"
:size="22"
class="text-10px"
fallback-src="/logo.png"
:render-placeholder="() => null"
:intersection-observer-options="{
root: '#image-chat-msgInput'
}">
{{ item.name.slice(0, 1) }}
</n-avatar>
<span> {{ item.name }}</span>
</n-flex>
</template>
@@ -148,6 +132,7 @@ import { onKeyStroke } from '@vueuse/core'
import { type } from '@tauri-apps/plugin-os'
import { useUserInfo } from '@/hooks/useCached.ts'
import { useMitt } from '@/hooks/useMitt.ts'
import { AvatarUtils } from '@/utils/avatarUtils'
const settingStore = useSettingStore()
const { themes } = storeToRefs(settingStore)

View File

@@ -100,7 +100,7 @@
<template v-else>
<div class="box-item cursor-default">
<n-flex align="center" :size="10">
<n-avatar round :size="40" :src="activeItem.avatar" />
<n-avatar round :size="40" :src="AvatarUtils.getAvatarUrl(activeItem.avatar)" />
<p class="text-(14px --text-color)">{{ activeItem.name }}</p>
@@ -122,16 +122,8 @@
<n-flex align="center" justify="center" :size="20">
<template v-for="(item, _index) in userList" :key="_index">
<n-flex v-if="item.avatar" vertical justify="center" align="center" :size="10">
<n-avatar round :size="30" :src="item.avatar" />
<p class="text-(10px --text-color center) w-30px truncate">{{ item.name }}</p>
</n-flex>
<n-flex v-else vertical justify="center" align="center" :size="10">
<n-avatar class="text-10px" round :size="30">
{{ item.name.slice(0, 1) }}
</n-avatar>
<n-flex vertical justify="center" align="center" :size="10">
<n-avatar round :size="30" :src="AvatarUtils.getAvatarUrl(item.avatar)" />
<p class="text-(10px --text-color center) w-30px truncate">{{ item.name }}</p>
</n-flex>
@@ -191,6 +183,7 @@ import { useChatStore } from '@/stores/chat.ts'
import { useGroupStore } from '@/stores/group.ts'
import { useUserInfo } from '@/hooks/useCached.ts'
import { useContactStore } from '@/stores/contacts.ts'
import { AvatarUtils } from '@/utils/avatarUtils'
// 使用useDisplayMedia获取屏幕共享的媒体流
const { stream, start, stop } = useDisplayMedia()

View File

@@ -122,28 +122,14 @@
:content="item"
:menu="chatStore.isGroup ? optionsList : void 0"
:special-menu="report">
<!-- 没有头像时候显示 -->
<n-avatar
round
v-if="avatarExists(item.fromUser.uid)"
:size="34"
:color="'#909090'"
@click="selectKey = item.message.id"
class="select-none"
:src="getAvatarSrc(item.fromUser.uid)"
:class="item.fromUser.uid === userUid ? '' : 'mr-10px'">
{{ avatarExists(item.fromUser.uid) }}
</n-avatar>
<!-- 存在头像时候显示 -->
<n-avatar
round
v-else
:size="34"
@click="selectKey = item.message.id"
class="select-none"
:src="getAvatarSrc(item.fromUser.uid)"
:class="item.fromUser.uid === userUid ? '' : 'mr-10px'">
</n-avatar>
:class="item.fromUser.uid === userUid ? '' : 'mr-10px'" />
</ContextMenu>
</template>
<!-- 用户个人信息框 -->
@@ -393,6 +379,7 @@ import { useChatStore } from '@/stores/chat.ts'
import { type } from '@tauri-apps/plugin-os'
import { useUserStore } from '@/stores/user.ts'
import { useNetwork } from '@vueuse/core'
import { AvatarUtils } from '@/utils/avatarUtils'
const { activeItem } = defineProps<{
activeItem: SessionItem
@@ -471,12 +458,8 @@ watch(chatMessageList, (value, oldValue) => {
/** 获取用户头像 */
const getAvatarSrc = (uid: number) => {
return uid === userUid.value ? userStore.userInfo.avatar : useUserInfo(uid).value.avatar
}
/** 头像是否存在 */
const avatarExists = (uid: number) => {
return getAvatarSrc(uid) ? void 0 : useUserInfo(uid).value.name?.slice(0, 1)
const avatar = uid === userUid.value ? userStore.userInfo.avatar : useUserInfo(uid).value.avatar
return AvatarUtils.getAvatarUrl(avatar as string)
}
// 当鼠标进入时触发的处理函数

View File

@@ -71,36 +71,18 @@
:special-menu="report">
<n-flex @click="selectKey = item.uid" :key="item.uid" :size="10" align="center" class="item">
<n-avatar
v-if="item.avatar"
lazy
round
class="grayscale"
:class="{ 'grayscale-0': item.activeStatus === OnlineEnum.ONLINE }"
:color="'#fff'"
:size="24"
:src="item.avatar"
:src="AvatarUtils.getAvatarUrl(item.avatar)"
fallback-src="/logo.png"
:render-placeholder="() => null"
:intersection-observer-options="{
root: '#image-chat-sidebar'
}"></n-avatar>
<n-avatar
v-else
lazy
round
class="grayscale text-10px"
:class="{ 'grayscale-0': item.activeStatus === OnlineEnum.ONLINE }"
:color="'rgba(19, 152, 127, 0.4)'"
:size="24"
:src="item.avatar"
fallback-src="/logo.png"
:render-placeholder="() => null"
:intersection-observer-options="{
root: '#image-chat-sidebar'
}">
{{ item.name?.slice(0, 1) }}
</n-avatar>
}" />
<span class="text-12px truncate flex-1">{{ item.name }}</span>
<div v-if="item.uid === 1" class="flex p-4px rounded-4px bg-#f5dadf size-fit select-none">
<span class="text-(10px #d5304f)">群主</span>
@@ -129,6 +111,7 @@ import { useUserInfo } from '@/hooks/useCached.ts'
import { useGlobalStore } from '@/stores/global.ts'
import type { UserItem } from '@/services/types.ts'
import { useDebounceFn } from '@vueuse/core'
import { AvatarUtils } from '@/utils/avatarUtils'
const groupStore = useGroupStore()
const globalStore = useGlobalStore()

View File

@@ -1,6 +1,6 @@
<template>
<n-scrollbar style="max-height: 290px" class="p-[14px_14px_0_14px] box-border w-460px h-290px select-none">
<transition name="fade" mode="out-in" appear>
<transition name="fade" mode="out-in">
<div :key="activeIndex" class="emoji-content">
<!-- 最近使用 -->
<div v-if="activeIndex === 0">

View File

@@ -25,23 +25,10 @@
<!-- 头像 -->
<n-flex justify="center">
<n-avatar
v-if="editInfo.content.avatar"
:size="80"
:src="editInfo.content.avatar"
:src="AvatarUtils.getAvatarUrl(editInfo.content.avatar)"
round
style="border: 3px solid #fff" />
<n-avatar
v-else
:size="80"
:color="'#909090'"
:src="editInfo.content.avatar"
round
class="text-22px"
style="border: 3px solid #fff">
{{ localUserInfo.name?.slice(0, 1) }}
<!-- {{ editInfo.content.name?.slice(0, 1) }} -->
</n-avatar>
</n-flex>
<n-flex v-if="currentBadge" align="center" justify="center">
<span class="text-(14px #707070)">当前佩戴的徽章:</span>
@@ -128,6 +115,7 @@ import { type } from '@tauri-apps/plugin-os'
import { useCommon } from '@/hooks/useCommon.ts'
import { useUserStore } from '@/stores/user.ts'
import { UserInfoType } from '@/services/types'
import { AvatarUtils } from '@/utils/avatarUtils'
let localUserInfo = ref<Partial<UserInfoType>>({})
const userStore = useUserStore()

View File

@@ -8,17 +8,7 @@
<template #trigger>
<!-- 头像 -->
<div class="relative size-34px rounded-50% cursor-pointer">
<n-avatar
v-if="avatarExists"
:color="'#909090'"
:size="34"
:src="userStore.userInfo.avatar"
fallback-src="/logo.png"
round>
{{ avatarExists }}
</n-avatar>
<n-avatar v-else :size="34" :src="userStore.userInfo.avatar" fallback-src="/logo.png" round />
<n-avatar :color="'#909090'" :size="34" :src="avatarSrc" fallback-src="/logo.png" round />
<div
class="bg-[--left-bg-color] text-10px rounded-50% size-12px absolute bottom--2px right--2px border-(2px solid [--left-bg-color])"
@@ -37,23 +27,11 @@
<n-flex :size="25" align="center" justify="space-between" class="select-none cursor-default">
<n-flex>
<n-avatar
v-if="avatarExists"
:color="'#909090'"
:src="userStore.userInfo.avatar"
:src="avatarSrc"
round
fallback-src="/logo.png"
class="size-68px text-20px select-none cursor-default">
{{ avatarExists }}
</n-avatar>
<n-avatar
v-else
:color="'#909090'"
:src="userStore.userInfo.avatar"
round
fallback-src="/logo.png"
class="size-68px text-20px select-none cursor-default">
</n-avatar>
class="size-68px text-20px select-none cursor-default" />
<n-flex :size="10" class="text-[--text-color]" justify="center" vertical>
<span class="text-18px">{{ userStore.userInfo.name }}</span>
@@ -104,9 +82,10 @@
<script setup lang="ts">
import { leftHook } from '../hook.ts'
import { useUserStore } from '@/stores/user.ts'
import { AvatarUtils } from '@/utils/avatarUtils'
const userStore = useUserStore()
const avatarExists = computed(() => (userStore.userInfo.avatar ? void 0 : userStore.userInfo.name?.slice(0, 1)))
const avatarSrc = computed(() => AvatarUtils.getAvatarUrl(userStore.userInfo.avatar as string))
const { shrinkStatus, url, infoShow, bgColor, title, themeColor, openContent, handleEditing } = leftHook()
</script>
<style lang="scss" scoped>

View File

@@ -3,7 +3,7 @@
<n-scrollbar style="max-height: 280px">
<n-flex :size="26" class="z-10 p-[18px_18px_36px_18px] box-border w-full">
<template v-for="(plugin, index) in allPlugins" :key="index">
<Transition name="fade" mode="out-in">
<Transition name="state-change" mode="out-in">
<!-- 未安装和下载中状态 -->
<n-flex
v-if="plugin.state === PluginEnum.NOT_INSTALLED || plugin.state === PluginEnum.DOWNLOADING"
@@ -233,7 +233,18 @@ onUnmounted(() => {
@use '@/styles/scss/global/variable.scss' as *;
.box {
@apply relative select-none custom-shadow cursor-pointer size-fit w-100px h-100px rounded-8px overflow-hidden;
transition: all 0.2s;
transition: all 0.3s ease-in-out;
&.state-change-enter-active,
&.state-change-leave-active {
transition: all 0.3s ease-in-out;
}
&.state-change-enter-from,
&.state-change-leave-to {
opacity: 0;
transform: scale(0.9);
}
.flash {
position: absolute;

View File

@@ -40,7 +40,7 @@
<n-tabs
:value="viewMode"
:on-update:value="(v) => (viewMode = v)"
:on-update:value="(v: any) => (viewMode = v)"
class="w-76px h-28px mr-22px"
type="segment"
animated>

View File

@@ -13,7 +13,7 @@
justify="center"
:size="20"
class="login-box relative h-160px w-full select-none">
<n-avatar :size="120" round bordered :src="userStore.userInfo.avatar" />
<n-avatar :size="120" round bordered :src="AvatarUtils.getAvatarUrl(userStore.userInfo.avatar)" />
<n-flex vertical justify="center" :size="20">
<p class="text-(24px [--chat-text-color]) font-500">{{ userStore.userInfo.name }}</p>
@@ -119,7 +119,7 @@
<template #default="{ item }">
<n-flex align="center" justify="space-between" class="mt-18px">
<n-flex align="center">
<n-avatar :size="36" round bordered :src="item.avatar" />
<n-avatar :size="36" round bordered :src="AvatarUtils.getAvatarUrl(item.avatar)" />
<p>{{ item.user }}</p>
<p class="text-(12px #707070)">{{ item.content }}</p>
</n-flex>
@@ -139,6 +139,7 @@ import { useWindowState } from '@/hooks/useWindowState.ts'
import { type } from '@tauri-apps/plugin-os'
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
import { useUserStore } from '@/stores/user.ts'
import { AvatarUtils } from '@/utils/avatarUtils'
useWindowState(WebviewWindow.getCurrent().label)
const userStore = useUserStore()

View File

@@ -23,7 +23,7 @@
<!-- 头像和插件 -->
<n-flex align="center" justify="space-between" :size="0">
<n-flex align="center">
<n-avatar bordered round :src="userStore.userInfo.avatar" :size="48" />
<n-avatar bordered round :src="AvatarUtils.getAvatarUrl(userStore.userInfo.avatar)" :size="48" />
<n-flex vertical>
<p class="text-(14px [--chat-text-color]) font-500">{{ userStore.userInfo.name }}</p>
<p class="text-(12px #909090)">剩余28天过期</p>
@@ -141,6 +141,7 @@ import { useMitt } from '@/hooks/useMitt.ts'
import { VueDraggable } from 'vue-draggable-plus'
import router from '@/router'
import { useUserStore } from '@/stores/user.ts'
import { AvatarUtils } from '@/utils/avatarUtils'
const userStore = useUserStore()
const activeItem = ref(0)

View File

@@ -27,6 +27,8 @@ export type LoginUserReq = {
}
export type RegisterUserReq = {
/** 默认随机头像 */
avatar: string
/** 昵称 */
name: string
/** 账号 */

41
src/utils/avatarUtils.ts Normal file
View File

@@ -0,0 +1,41 @@
/**
* 用于处理头像相关操作的实用类
*/
export class AvatarUtils {
private static readonly DEFAULT_AVATAR_RANGE = {
start: '001',
end: '021'
}
private static readonly RANGE_START = parseInt(AvatarUtils.DEFAULT_AVATAR_RANGE.start, 10)
private static readonly RANGE_END = parseInt(AvatarUtils.DEFAULT_AVATAR_RANGE.end, 10)
/**
* 检查头像字符串是否为默认头像 (001-021)
* @param avatar - 要检查的头像字符串
* @returns 布尔值指示是否是默认头像
*/
public static isDefaultAvatar(avatar: string): boolean {
// 快速判断如果为空或长度不是3直接返回false
if (!avatar || avatar.length !== 3) return false
// 检查是否全是数字
const num = parseInt(avatar, 10)
if (isNaN(num)) return false
// 数字范围检查 (001-021)
return num >= this.RANGE_START && num <= this.RANGE_END
}
/**
* 根据头像值获取头像URL
* @param avatar - 头像字符串或URL
* @returns 头像字符串或URL
*/
public static getAvatarUrl(avatar: string): string {
if (this.isDefaultAvatar(avatar)) {
return `/avatar/${avatar}.png`
}
return avatar
}
}

View File

@@ -47,7 +47,11 @@
:size="16"
class="h-full backdrop-blur-md rounded-8px">
<n-flex vertical align="center" justify="center" :size="30" class="mt--75px">
<n-avatar round style="border: 2px solid #f1f1f1" :size="120" :src="userStore.userInfo.avatar" />
<n-avatar
round
style="border: 2px solid #f1f1f1"
:size="120"
:src="AvatarUtils.getAvatarUrl(userStore.userInfo.avatar)" />
<p class="text-(24px #f1f1f1) font-500">{{ userStore.userInfo.name }}</p>
<!-- 密码输入框 -->
@@ -118,6 +122,7 @@ import { InputInst, lightTheme } from 'naive-ui'
import { getWeekday } from '@/utils/Day.ts'
import dayjs from 'dayjs'
import { useUserStore } from '@/stores/user.ts'
import { AvatarUtils } from '@/utils/avatarUtils'
const appWindow = WebviewWindow.getCurrent()
const settingStore = useSettingStore()

View File

@@ -29,28 +29,14 @@
:key="item.uid">
<n-flex align="center" :size="10" class="h-75px pl-6px pr-8px flex-1 truncate">
<n-avatar
v-if="useUserInfo(item.uid).value.avatar"
round
bordered
:size="44"
class="grayscale"
:class="{ 'grayscale-0': item.activeStatus === OnlineEnum.ONLINE }"
:src="useUserInfo(item.uid).value.avatar"
:src="AvatarUtils.getAvatarUrl(useUserInfo(item.uid).value.avatar)"
fallback-src="/logo.png" />
<n-avatar
v-else
round
bordered
:color="'#909090'"
:size="44"
class="grayscale"
:class="{ 'grayscale-0': item.activeStatus === OnlineEnum.ONLINE }"
:src="useUserInfo(item.uid).value.avatar"
fallback-src="/logo.png">
{{ useUserInfo(item.uid).value.name?.slice(0, 1) }}
</n-avatar>
<n-flex vertical justify="space-between" class="h-fit flex-1 truncate">
<span class="text-14px leading-tight flex-1 truncate">{{
useUserInfo(item.uid).value.name
@@ -96,6 +82,7 @@ import { useMitt } from '@/hooks/useMitt.ts'
import { MittEnum, OnlineEnum, RoomTypeEnum } from '@/enums'
import { useContactStore } from '@/stores/contacts.ts'
import { useUserInfo } from '@/hooks/useCached.ts'
import { AvatarUtils } from '@/utils/avatarUtils'
const menuList = ref([
{ label: '添加分组', icon: 'plus' },

View File

@@ -16,13 +16,14 @@
<template v-for="(item, _index) in historyList" :key="_index">
<n-flex align="center" :size="14" class="p-6px cursor-pointer rounded-8px hover:bg-[--list-hover-color]">
<n-avatar :size="38" round bordered :src="item.avatar" />
<n-avatar :size="38" round bordered :src="AvatarUtils.getAvatarUrl(item.avatar)" />
<p class="text-(14px [--text-color])">{{ item.name }}</p>
</n-flex>
</template>
</n-flex>
</template>
<script setup lang="ts">
import { AvatarUtils } from '@/utils/avatarUtils'
const historyList = [
{
avatar: 'https://picsum.photos/140?1',

View File

@@ -15,11 +15,7 @@
@dblclick="handleMsgDblclick(item)"
@select="$event.click(item)">
<n-flex :size="10" align="center" class="h-75px pl-6px pr-8px flex-1">
<n-avatar v-if="item.avatar" :size="44" :src="item.avatar" bordered fallback-src="/logo.png" round />
<n-avatar v-else :color="'#909090'" :size="44" :src="item.avatar" bordered fallback-src="/logo.png" round>
{{ item.name?.slice(0, 1) }}
</n-avatar>
<n-avatar :size="44" :src="AvatarUtils.getAvatarUrl(item.avatar)" bordered fallback-src="/logo.png" round />
<n-flex class="h-fit flex-1 truncate" justify="space-between" vertical>
<n-flex :size="4" align="center" class="flex-1 truncate" justify="space-between">
@@ -66,6 +62,7 @@ import { useUserInfo } from '@/hooks/useCached.ts'
import { renderReplyContent } from '@/utils/RenderReplyContent.ts'
import { useCommon } from '@/hooks/useCommon.ts'
import SysNTF from '@/components/common/SystemNotification.tsx'
import { AvatarUtils } from '@/utils/avatarUtils'
const chatStore = useChatStore()
const globalStore = useGlobalStore()

View File

@@ -9,13 +9,8 @@
<!-- 头像 -->
<n-flex justify="center" class="w-full pt-35px" data-tauri-drag-region>
<n-avatar
v-if="info.avatar"
class="size-80px rounded-50% bg-#b6d6d9ff border-(2px solid #fff)"
:src="info.avatar || '/logo.png'" />
<n-avatar v-else class="size-80px text-20px rounded-50% bg-#b6d6d9ff border-(2px solid #fff)">
{{ info.name.slice(0, 1) }}
</n-avatar>
:src="AvatarUtils.getAvatarUrl(info.avatar || '/logo.png')" />
</n-flex>
<!-- 登录菜单 -->
@@ -54,10 +49,7 @@
@click="giveAccount(item)"
class="p-8px cursor-pointer hover:bg-#f3f3f3 hover:rounded-6px">
<div class="flex-between-center">
<n-avatar v-if="item.avatar" :src="item.avatar" class="size-28px bg-#ccc rounded-50%" />
<n-avatar v-else :src="item.avatar" :color="'#909090'" class="size-28px text-10px bg-#ccc rounded-50%">
{{ item.name?.slice(0, 1) }}
</n-avatar>
<n-avatar :src="AvatarUtils.getAvatarUrl(item.avatar)" class="size-28px bg-#ccc rounded-50%" />
<p class="text-14px color-#505050">{{ item.account }}</p>
<svg @click.stop="delAccount(item)" class="w-12px h-12px">
<use href="#close"></use>
@@ -115,7 +107,7 @@
:size="110"
:color="'#fff'"
class="border-(2px solid #fff)"
:src="userStore.userInfo.avatar || '/logo.png'" />
:src="AvatarUtils.getAvatarUrl(userStore.userInfo.avatar || '/logo.png')" />
</n-flex>
<n-flex justify="center">
@@ -174,6 +166,7 @@ import { useUserStore } from '@/stores/user.ts'
import { UserInfoType } from '@/services/types.ts'
import { useSettingStore } from '@/stores/setting.ts'
import { invoke } from '@tauri-apps/api/core'
import { AvatarUtils } from '@/utils/avatarUtils'
const settingStore = useSettingStore()
const userStore = useUserStore()

View File

@@ -107,7 +107,8 @@ import Validation from '@/components/common/Validation.vue'
/** 账号信息 */
const info = unref(
ref({
ref<RegisterUserReq>({
avatar: '',
account: '',
password: '',
name: ''
@@ -176,9 +177,17 @@ const register = async () => {
btnEnable.value = true
loading.value = true
btnText.value = '注册中...'
// 随机生成头像编号
const avatarNum = Math.floor(Math.random() * 21) + 1
const avatarId = avatarNum.toString().padStart(3, '0')
info.avatar = avatarId
// 更新按钮文本
btnText.value = '正在分配默认头像...'
// 模拟头像分配延迟
setTimeout(() => {
// 注册
apis
.register({ ...info } as RegisterUserReq)
.register({ ...info })
.then(() => {
window.$message.success('注册成功')
btnText.value = '注册'
@@ -194,6 +203,7 @@ const register = async () => {
btnEnable.value = false
btnText.value = '注册'
})
}, 800) // 添加800ms延迟来展示头像分配过程
}
})
}