diff --git a/packages/design/components/Avatar/index.tsx b/packages/design/components/Avatar/index.tsx
index 4e9216bc..ac0364a9 100644
--- a/packages/design/components/Avatar/index.tsx
+++ b/packages/design/components/Avatar/index.tsx
@@ -9,6 +9,8 @@ import type { AvatarProps as AntdAvatarProps } from 'antd/lib/avatar';
import { getTextColorHex } from './utils';
import { isValidStr } from '../utils';
+export { getTextColorHex };
+
export interface AvatarProps extends AntdAvatarProps {
name?: string;
isOnline?: boolean;
diff --git a/packages/design/components/index.ts b/packages/design/components/index.ts
index c948495d..795ce292 100644
--- a/packages/design/components/index.ts
+++ b/packages/design/components/index.ts
@@ -1,5 +1,5 @@
export { AutoFolder } from './AutoFolder';
-export { Avatar } from './Avatar';
+export { Avatar, getTextColorHex } from './Avatar';
export { AvatarWithPreview } from './AvatarWithPreview';
export { CombinedAvatar } from './Avatar/combined';
export { DelayTip } from './DelayTip';
diff --git a/web/src/components/UserProfileContainer.tsx b/web/src/components/UserProfileContainer.tsx
new file mode 100644
index 00000000..20c2dd5b
--- /dev/null
+++ b/web/src/components/UserProfileContainer.tsx
@@ -0,0 +1,42 @@
+import { fetchImagePrimaryColor } from '@/utils/image-helper';
+import React from 'react';
+import { AvatarWithPreview, getTextColorHex } from 'tailchat-design';
+import { useAsync, UserBaseInfo } from 'tailchat-shared';
+
+/**
+ * 用户信息容器
+ */
+export const UserProfileContainer: React.FC<{ userInfo: UserBaseInfo }> =
+ React.memo((props) => {
+ const { userInfo } = props;
+ const { value: bannerColor } = useAsync(async () => {
+ if (!userInfo.avatar) {
+ return getTextColorHex(userInfo.nickname);
+ }
+
+ const rgba = await fetchImagePrimaryColor(userInfo.avatar);
+ return `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a})`;
+ }, [userInfo.avatar]);
+ return (
+
+
+
+
+
+
{props.children}
+
+ );
+ });
+UserProfileContainer.displayName = 'UserProfileContainer';
diff --git a/web/src/components/popover/GroupUserPopover.tsx b/web/src/components/popover/GroupUserPopover.tsx
index f1c298bc..880816c9 100644
--- a/web/src/components/popover/GroupUserPopover.tsx
+++ b/web/src/components/popover/GroupUserPopover.tsx
@@ -1,28 +1,34 @@
+import { fetchImagePrimaryColor } from '@/utils/image-helper';
import { Tag } from 'antd';
-import React from 'react';
-import { AvatarWithPreview } from 'tailchat-design';
+import React, { useEffect } from 'react';
import { t, UserBaseInfo } from 'tailchat-shared';
+import { UserProfileContainer } from '../UserProfileContainer';
export const GroupUserPopover: React.FC<{
userInfo: UserBaseInfo;
}> = React.memo((props) => {
const { userInfo } = props;
+ useEffect(() => {
+ if (userInfo.avatar) {
+ fetchImagePrimaryColor(userInfo.avatar).then((rgba) => {
+ console.log('fetchImagePrimaryColor', rgba);
+ });
+ }
+ }, [userInfo.avatar]);
+
return (
-
-
-
- {userInfo.nickname}
- #{userInfo.discriminator}
-
+
+
+
+ {userInfo.nickname}
+ #{userInfo.discriminator}
+
-
- {userInfo.temporary && {t('游客')}}
-
+
+ {userInfo.temporary && {t('游客')}}
+
+
);
});
diff --git a/web/src/styles/antd/overwrite.less b/web/src/styles/antd/overwrite.less
index 5f7628ea..6e7abc8a 100644
--- a/web/src/styles/antd/overwrite.less
+++ b/web/src/styles/antd/overwrite.less
@@ -57,3 +57,9 @@
vertical-align: text-top;
}
}
+
+.ant-popover {
+ .ant-popover-inner-content {
+ background-color: inherit;
+ }
+}
diff --git a/web/src/utils/image-helper.ts b/web/src/utils/image-helper.ts
new file mode 100644
index 00000000..e29b9f3e
--- /dev/null
+++ b/web/src/utils/image-helper.ts
@@ -0,0 +1,57 @@
+/**
+ * 加载图片
+ */
+async function loadImage(url: string): Promise
{
+ const el = document.createElement('img');
+ return new Promise((resolve, reject) => {
+ el.onload = () => resolve(el);
+ el.onerror = reject;
+ el.src = url;
+ el.crossOrigin = 'Anonymous';
+ });
+}
+
+/**
+ * 获取图片的主要颜色
+ */
+export async function fetchImagePrimaryColor(imageUrl: string) {
+ const img = await loadImage(imageUrl);
+ const canvas = document.createElement('canvas');
+
+ canvas.width = img.width;
+ canvas.height = img.height;
+ const ctx = canvas.getContext('2d');
+ if (!ctx) {
+ return { r: 0, g: 0, b: 0, a: 0 };
+ }
+
+ ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
+
+ let r = 0;
+ let g = 0;
+ let b = 0;
+ let a = 0;
+
+ for (let row = 0; row < imageData.height; row++) {
+ for (let col = 0; col < imageData.width; col++) {
+ r += imageData.data[(imageData.width * row + col) * 4];
+ g += imageData.data[(imageData.width * row + col) * 4 + 1];
+ b += imageData.data[(imageData.width * row + col) * 4 + 2];
+ a += imageData.data[(imageData.width * row + col) * 4 + 3];
+ }
+ }
+
+ const sum = imageData.width * imageData.height;
+ r = Math.round(r / sum);
+ g = Math.round(g / sum);
+ b = Math.round(b / sum);
+ a = Math.round(a / sum);
+
+ return {
+ r,
+ g,
+ b,
+ a,
+ };
+}
diff --git a/web/tailwind.config.js b/web/tailwind.config.js
index 6cafe220..7b93f54c 100644
--- a/web/tailwind.config.js
+++ b/web/tailwind.config.js
@@ -55,6 +55,9 @@ module.exports = {
},
extend: {
colors: {
+ inherit: {
+ DEFAULT: 'inherit',
+ },
navbar: {
light: colors.coolGray[300],
dark: colors.coolGray[900],