diff --git a/src/components/Sider/ChatSider/index.tsx b/src/components/Sider/ChatSider/index.tsx
index 2a96748..5b253f0 100644
--- a/src/components/Sider/ChatSider/index.tsx
+++ b/src/components/Sider/ChatSider/index.tsx
@@ -6,7 +6,7 @@ import ChatItem from './ChatItem'
function ChatSider() {
const chats = [
{
- id: 'test-chat',
+ _id: 'test-chat',
avatar: 'https://avatars.githubusercontent.com/u/74760542?v=4',
nickname: 'Test User',
note: 'Test User',
@@ -26,7 +26,7 @@ function ChatSider() {
{chats.map(chat => (
))}
diff --git a/src/components/Sider/ContactSider/ContactItem/index.module.less b/src/components/Sider/ContactSider/ContactItem/index.module.less
new file mode 100644
index 0000000..3c4a3c4
--- /dev/null
+++ b/src/components/Sider/ContactSider/ContactItem/index.module.less
@@ -0,0 +1,6 @@
+@import url("@/themes/index.less");
+
+.contactItem {
+ padding: 10px;
+ background-color: @colorPrimary;
+}
\ No newline at end of file
diff --git a/src/components/Sider/ContactSider/ContactItem/index.tsx b/src/components/Sider/ContactSider/ContactItem/index.tsx
new file mode 100644
index 0000000..0a5631d
--- /dev/null
+++ b/src/components/Sider/ContactSider/ContactItem/index.tsx
@@ -0,0 +1,48 @@
+import { Badge, Flex, Typography } from 'antd'
+import { useNavigate } from 'react-router-dom'
+import { useMeasure } from 'react-use'
+import styles from './index.module.less'
+import type { Contact } from '@/typings'
+import { useThemeToken, useTime } from '@/hooks'
+
+interface ContactItemProps {
+ contact: Contact
+}
+
+function ContactItem({ contact }: ContactItemProps) {
+ const [ref, { width }] = useMeasure
()
+ const { token } = useThemeToken()
+ const time = useTime()
+ const { updatedAt, avatar, sender, count, note, nickname, message } = contact
+ const navigate = useNavigate()
+
+ return (
+ {
+ navigate(`/contact/${contact.id}`)
+ }}
+ gap={10}
+ className={styles.contactItem}
+ >
+
+

+
+
+
+ {note || nickname}
+ {time(updatedAt).fromNow()}
+
+
+
+ {sender ? `${sender}:` : ''}
+ {message}
+
+
+
+
+
+ )
+}
+
+export default ContactItem
diff --git a/src/components/Sider/ContactSider/index.module.less b/src/components/Sider/ContactSider/index.module.less
new file mode 100644
index 0000000..48bb509
--- /dev/null
+++ b/src/components/Sider/ContactSider/index.module.less
@@ -0,0 +1,14 @@
+.sider {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+
+ .topBar {
+ padding: 20px 10px;
+ background-color: #fff;
+ }
+
+ .list {
+ flex: 1;
+ }
+}
\ No newline at end of file
diff --git a/src/components/Sider/ContactSider/index.tsx b/src/components/Sider/ContactSider/index.tsx
index d0789f5..4abe3b7 100644
--- a/src/components/Sider/ContactSider/index.tsx
+++ b/src/components/Sider/ContactSider/index.tsx
@@ -1,8 +1,39 @@
-import React from 'react'
+import { PlusOutlined } from '@ant-design/icons'
+import { Button, Flex, Input } from 'antd'
+import { useNavigate } from 'react-router-dom'
+import ContactItem from './ContactItem'
+import styles from './index.module.less'
+import type { Contact } from '@/typings'
function ContactSider() {
+ const navigate = useNavigate()
+ const contacts = [
+ {
+ _id: '1234',
+ },
+ ] as Contact[]
+
return (
- ContactSider
+
+
+
+ }
+ onClick={() => {
+ navigate('/contact/search')
+ }}
+ />
+
+
+ {contacts.map(contact => (
+
+ ))}
+
+
)
}
diff --git a/src/layouts/BasicLayout.tsx b/src/layouts/BasicLayout.tsx
index 5a18f62..b3d86a2 100644
--- a/src/layouts/BasicLayout.tsx
+++ b/src/layouts/BasicLayout.tsx
@@ -1,11 +1,32 @@
import { MessageOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons'
+import type { GlobalToken } from 'antd'
import { ConfigProvider, Layout, Menu } from 'antd'
import { useEffect, useState } from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
-import { useAppStore } from '@/hooks'
+import { useAppStore, useThemeToken } from '@/hooks'
import { AUTH_TOKEN_KEY } from '@/config'
import Loading from '@/components/Loading'
+function AddThemeToVars() {
+ const { token: realToken } = useThemeToken()
+
+ useEffect(() => {
+ realToken.fontSizeSM = 12
+ const usefulToken = Object.keys(realToken).filter(_ => !/-[0-9]/.test(_))
+ usefulToken.forEach((key) => {
+ const needUnit = ['borderRadius', 'fontSize', 'size']
+ document.documentElement.style.setProperty(
+ `--${key}`,
+ needUnit.some(item => key.startsWith(item))
+ ? `${realToken[key as (keyof GlobalToken)]}px` as string
+ : realToken[key as (keyof GlobalToken)] as string,
+ )
+ })
+ }, [realToken])
+
+ return null
+}
+
function BasicLayout(props: any) {
const navigate = useNavigate()
const { pathname } = useLocation()
@@ -33,6 +54,7 @@ function BasicLayout(props: any) {
},
}}
>
+
+ )
+}
+
+export default ContactDetail
diff --git a/src/pages/Contact/ContactSearch/index.tsx b/src/pages/Contact/ContactSearch/index.tsx
new file mode 100644
index 0000000..7bca055
--- /dev/null
+++ b/src/pages/Contact/ContactSearch/index.tsx
@@ -0,0 +1,7 @@
+function ContactSearch() {
+ return (
+ ContactSearch
+ )
+}
+
+export default ContactSearch
diff --git a/src/pages/Contact/index.tsx b/src/pages/Contact/index.tsx
index 167842a..df7081a 100644
--- a/src/pages/Contact/index.tsx
+++ b/src/pages/Contact/index.tsx
@@ -1,11 +1,11 @@
import ContactSider from '@/components/Sider/ContactSider'
import MainLayout from '@/layouts/MainLayout'
-function Contact() {
+function Contact(props: any) {
return (
- 联系人
+ {props.children}
)
}
diff --git a/src/router/routes.tsx b/src/router/routes.tsx
index 481896c..11c4528 100644
--- a/src/router/routes.tsx
+++ b/src/router/routes.tsx
@@ -6,6 +6,8 @@ import Contact from '@/pages/Contact'
import Home from '@/pages/Home'
import Login from '@/pages/Login'
import Setting from '@/pages/Setting'
+import ContactDetail from '@/pages/Contact/ContactDetail'
+import ContactSearch from '@/pages/Contact/ContactSearch'
const routes: RouteObject[] = [
{
@@ -34,11 +36,17 @@ const routes: RouteObject[] = [
},
{
path: 'contact',
- element: ,
+ element:
+
+ ,
children: [
{
path: ':id',
- element: ,
+ element: ,
+ },
+ {
+ path: 'search',
+ element: ,
},
],
},
diff --git a/src/themes/custom.less b/src/themes/custom.less
new file mode 100644
index 0000000..1d7ec8a
--- /dev/null
+++ b/src/themes/custom.less
@@ -0,0 +1,12 @@
+@scrollbar-height: var(--scrollbar-height);
+@scrollbar-color: var(--scrollbar-color);
+@scrollbar-track-color: var(--scrollbar-track-color);
+@scrollbar-hover-color: var(--scrollbar-hover-color);
+@scrollbar-border-radius: var(--scrollbar-border-radius);
+@transition: var(--motionDurationMid) var(--motionEaseInOut);
+@boxShadow: var(--box-shadow);
+@boxShadowSecondary: var(--boxShadowSecondary);
+@boxShadowCard: var(--boxShadowCard);
+@headerHeight: var(--header-height);
+@colorBorderBg: var(--colorBorderBg);
+@colorBgTextHover: var(--colorBgTextHover);
diff --git a/src/themes/default.less b/src/themes/default.less
new file mode 100644
index 0000000..7bfa023
--- /dev/null
+++ b/src/themes/default.less
@@ -0,0 +1,275 @@
+/*
+* MapToken
+* https://ant.design/docs/react/customize-theme-cn#maptoken
+*/
+@blue: var(--blue);
+@purple: var(--purple);
+@cyan: var(--cyan);
+@green: var(--green);
+@magenta: var(--magenta);
+@pink: var(--pink);
+@red: var(--red);
+@orange: var(--orange);
+@yellow: var(--yellow);
+@volcano: var(--volcano);
+@geekblue: var(--geekblue);
+@gold: var(--gold);
+@lime: var(--lime);
+@colorPrimary: var(--colorPrimary);
+@colorSuccess: var(--colorSuccess);
+@colorWarning: var(--colorWarning);
+@colorError: var(--colorError);
+@colorInfo: var(--colorInfo);
+@colorTextBase: var(--colorTextBase);
+@colorBgBase: var(--colorBgBase);
+@fontFamily: var(--fontFamily);
+@fontFamilyCode: var(--fontFamilyCode);
+@fontSize: var(--fontSize);
+@lineWidth: var(--lineWidth);
+@lineType: var(--lineType);
+@motionUnit: var(--motionUnit);
+@motionBase: var(--motionBase);
+@motionEaseOutCirc: var(--motionEaseOutCirc);
+@motionEaseInOutCirc: var(--motionEaseInOutCirc);
+@motionEaseOut: var(--motionEaseOut);
+@motionEaseInOut: var(--motionEaseInOut);
+@motionEaseOutBack: var(--motionEaseOutBack);
+@motionEaseInBack: var(--motionEaseInBack);
+@motionEaseInQuint: var(--motionEaseInQuint);
+@motionEaseOutQuint: var(--motionEaseOutQuint);
+@borderRadius: var(--borderRadius);
+@sizeUnit: var(--sizeUnit);
+@sizeStep: var(--sizeStep);
+@sizePopupArrow: var(--sizePopupArrow);
+@controlHeight: var(--controlHeight);
+@zIndexBase: var(--zIndexBase);
+@zIndexPopupBase: var(--zIndexPopupBase);
+@opacityImage: var(--opacityImage);
+@wireframe: var(--wireframe);
+@blue1: var(--blue1);
+@blue2: var(--blue2);
+@blue3: var(--blue3);
+@blue4: var(--blue4);
+@blue5: var(--blue5);
+@blue6: var(--blue6);
+@blue7: var(--blue7);
+@blue8: var(--blue8);
+@blue9: var(--blue9);
+@blue10: var(--blue10);
+@purple1: var(--purple1);
+@purple2: var(--purple2);
+@purple3: var(--purple3);
+@purple4: var(--purple4);
+@purple5: var(--purple5);
+@purple6: var(--purple6);
+@purple7: var(--purple7);
+@purple8: var(--purple8);
+@purple9: var(--purple9);
+@purple10: var(--purple10);
+@cyan1: var(--cyan1);
+@cyan2: var(--cyan2);
+@cyan3: var(--cyan3);
+@cyan4: var(--cyan4);
+@cyan5: var(--cyan5);
+@cyan6: var(--cyan6);
+@cyan7: var(--cyan7);
+@cyan8: var(--cyan8);
+@cyan9: var(--cyan9);
+@cyan10: var(--cyan10);
+@green1: var(--green1);
+@green2: var(--green2);
+@green3: var(--green3);
+@green4: var(--green4);
+@green5: var(--green5);
+@green6: var(--green6);
+@green7: var(--green7);
+@green8: var(--green8);
+@green9: var(--green9);
+@green10: var(--green10);
+@magenta1: var(--magenta1);
+@magenta2: var(--magenta2);
+@magenta3: var(--magenta3);
+@magenta4: var(--magenta4);
+@magenta5: var(--magenta5);
+@magenta6: var(--magenta6);
+@magenta7: var(--magenta7);
+@magenta8: var(--magenta8);
+@magenta9: var(--magenta9);
+@magenta10: var(--magenta10);
+@pink1: var(--pink1);
+@pink2: var(--pink2);
+@pink3: var(--pink3);
+@pink4: var(--pink4);
+@pink5: var(--pink5);
+@pink6: var(--pink6);
+@pink7: var(--pink7);
+@pink8: var(--pink8);
+@pink9: var(--pink9);
+@pink10: var(--pink10);
+@red1: var(--red1);
+@red2: var(--red2);
+@red3: var(--red3);
+@red4: var(--red4);
+@red5: var(--red5);
+@red6: var(--red6);
+@red7: var(--red7);
+@red8: var(--red8);
+@red9: var(--red9);
+@red10: var(--red10);
+@orange1: var(--orange1);
+@orange2: var(--orange2);
+@orange3: var(--orange3);
+@orange4: var(--orange4);
+@orange5: var(--orange5);
+@orange6: var(--orange6);
+@orange7: var(--orange7);
+@orange8: var(--orange8);
+@orange9: var(--orange9);
+@orange10: var(--orange10);
+@yellow1: var(--yellow1);
+@yellow2: var(--yellow2);
+@yellow3: var(--yellow3);
+@yellow4: var(--yellow4);
+@yellow5: var(--yellow5);
+@yellow6: var(--yellow6);
+@yellow7: var(--yellow7);
+@yellow8: var(--yellow8);
+@yellow9: var(--yellow9);
+@yellow10: var(--yellow10);
+@volcano1: var(--volcano1);
+@volcano2: var(--volcano2);
+@volcano3: var(--volcano3);
+@volcano4: var(--volcano4);
+@volcano5: var(--volcano5);
+@volcano6: var(--volcano6);
+@volcano7: var(--volcano7);
+@volcano8: var(--volcano8);
+@volcano9: var(--volcano9);
+@volcano10: var(--volcano10);
+@geekblue1: var(--geekblue1);
+@geekblue2: var(--geekblue2);
+@geekblue3: var(--geekblue3);
+@geekblue4: var(--geekblue4);
+@geekblue5: var(--geekblue5);
+@geekblue6: var(--geekblue6);
+@geekblue7: var(--geekblue7);
+@geekblue8: var(--geekblue8);
+@geekblue9: var(--geekblue9);
+@geekblue10: var(--geekblue10);
+@gold1: var(--gold1);
+@gold2: var(--gold2);
+@gold3: var(--gold3);
+@gold4: var(--gold4);
+@gold5: var(--gold5);
+@gold6: var(--gold6);
+@gold7: var(--gold7);
+@gold8: var(--gold8);
+@gold9: var(--gold9);
+@gold10: var(--gold10);
+@lime1: var(--lime1);
+@lime2: var(--lime2);
+@lime3: var(--lime3);
+@lime4: var(--lime4);
+@lime5: var(--lime5);
+@lime6: var(--lime6);
+@lime7: var(--lime7);
+@lime8: var(--lime8);
+@lime9: var(--lime9);
+@lime10: var(--lime10);
+@colorText: var(--colorText);
+@colorTextSecondary: var(--colorTextSecondary);
+@colorTextTertiary: var(--colorTextTertiary);
+@colorTextQuaternary: var(--colorTextQuaternary);
+@colorFill: var(--colorFill);
+@colorFillSecondary: var(--colorFillSecondary);
+@colorFillTertiary: var(--colorFillTertiary);
+@colorFillQuaternary: var(--colorFillQuaternary);
+@colorBgLayout: var(--colorBgLayout);
+@colorBgContainer: var(--colorBgContainer);
+@colorBgElevated: var(--colorBgElevated);
+@colorBgSpotlight: var(--colorBgSpotlight);
+@colorBorder: var(--colorBorder);
+@colorBorderSecondary: var(--colorBorderSecondary);
+@colorPrimaryBg: var(--colorPrimaryBg);
+@colorPrimaryBgHover: var(--colorPrimaryBgHover);
+@colorPrimaryBorder: var(--colorPrimaryBorder);
+@colorPrimaryBorderHover: var(--colorPrimaryBorderHover);
+@colorPrimaryHover: var(--colorPrimaryHover);
+@colorPrimaryActive: var(--colorPrimaryActive);
+@colorPrimaryTextHover: var(--colorPrimaryTextHover);
+@colorPrimaryText: var(--colorPrimaryText);
+@colorPrimaryTextActive: var(--colorPrimaryTextActive);
+@colorSuccessBg: var(--colorSuccessBg);
+@colorSuccessBgHover: var(--colorSuccessBgHover);
+@colorSuccessBorder: var(--colorSuccessBorder);
+@colorSuccessBorderHover: var(--colorSuccessBorderHover);
+@colorSuccessHover: var(--colorSuccessHover);
+@colorSuccessActive: var(--colorSuccessActive);
+@colorSuccessTextHover: var(--colorSuccessTextHover);
+@colorSuccessText: var(--colorSuccessText);
+@colorSuccessTextActive: var(--colorSuccessTextActive);
+@colorErrorBg: var(--colorErrorBg);
+@colorErrorBgHover: var(--colorErrorBgHover);
+@colorErrorBorder: var(--colorErrorBorder);
+@colorErrorBorderHover: var(--colorErrorBorderHover);
+@colorErrorHover: var(--colorErrorHover);
+@colorErrorActive: var(--colorErrorActive);
+@colorErrorTextHover: var(--colorErrorTextHover);
+@colorErrorText: var(--colorErrorText);
+@colorErrorTextActive: var(--colorErrorTextActive);
+@colorWarningBg: var(--colorWarningBg);
+@colorWarningBgHover: var(--colorWarningBgHover);
+@colorWarningBorder: var(--colorWarningBorder);
+@colorWarningBorderHover: var(--colorWarningBorderHover);
+@colorWarningHover: var(--colorWarningHover);
+@colorWarningActive: var(--colorWarningActive);
+@colorWarningTextHover: var(--colorWarningTextHover);
+@colorWarningText: var(--colorWarningText);
+@colorWarningTextActive: var(--colorWarningTextActive);
+@colorInfoBg: var(--colorInfoBg);
+@colorInfoBgHover: var(--colorInfoBgHover);
+@colorInfoBorder: var(--colorInfoBorder);
+@colorInfoBorderHover: var(--colorInfoBorderHover);
+@colorInfoHover: var(--colorInfoHover);
+@colorInfoActive: var(--colorInfoActive);
+@colorInfoTextHover: var(--colorInfoTextHover);
+@colorInfoText: var(--colorInfoText);
+@colorInfoTextActive: var(--colorInfoTextActive);
+@colorBgMask: var(--colorBgMask);
+@colorWhite: var(--colorWhite);
+@fontSizeSM: var(--fontSizeSM);
+@fontSizeLG: var(--fontSizeLG);
+@fontSizeXL: var(--fontSizeXL);
+@fontSizeHeading1: var(--fontSizeHeading1);
+@fontSizeHeading2: var(--fontSizeHeading2);
+@fontSizeHeading3: var(--fontSizeHeading3);
+@fontSizeHeading4: var(--fontSizeHeading4);
+@fontSizeHeading5: var(--fontSizeHeading5);
+@lineHeight: var(--lineHeight);
+@lineHeightLG: var(--lineHeightLG);
+@lineHeightSM: var(--lineHeightSM);
+@lineHeightHeading1: var(--lineHeightHeading1);
+@lineHeightHeading2: var(--lineHeightHeading2);
+@lineHeightHeading3: var(--lineHeightHeading3);
+@lineHeightHeading4: var(--lineHeightHeading4);
+@lineHeightHeading5: var(--lineHeightHeading5);
+@sizeXXL: var(--sizeXXL);
+@sizeXL: var(--sizeXL);
+@sizeLG: var(--sizeLG);
+@sizeMD: var(--sizeMD);
+@sizeMS: var(--sizeMS);
+@size: var(--size);
+@sizeSM: var(--sizeSM);
+@sizeXS: var(--sizeXS);
+@sizeXXS: var(--sizeXXS);
+@controlHeightSM: var(--controlHeightSM);
+@controlHeightXS: var(--controlHeightXS);
+@controlHeightLG: var(--controlHeightLG);
+@motionDurationFast: var(--motionDurationFast);
+@motionDurationMid: var(--motionDurationMid);
+@motionDurationSlow: var(--motionDurationSlow);
+@lineWidthBold: var(--lineWidthBold);
+@borderRadiusXS: var(--borderRadiusXS);
+@borderRadiusSM: var(--borderRadiusSM);
+@borderRadiusLG: var(--borderRadiusLG);
+@borderRadiusOuter: var(--borderRadiusOuter);
diff --git a/src/themes/index.less b/src/themes/index.less
new file mode 100644
index 0000000..65703ef
--- /dev/null
+++ b/src/themes/index.less
@@ -0,0 +1,2 @@
+@import url('@/themes/default.less');
+@import url('@/themes/custom.less');
diff --git a/src/typings/common.ts b/src/typings/common.ts
index 13b8f9a..50134d1 100644
--- a/src/typings/common.ts
+++ b/src/typings/common.ts
@@ -9,7 +9,7 @@ export interface Common {
/**
* id
*/
- id: string
+ _id: string
/**
* 更新时间
*/
@@ -26,3 +26,10 @@ export enum Language {
Zh = 'zh',
En = 'en',
}
+
+export enum Status {
+ Success = 'success',
+ Error = 'error',
+ Warning = 'warning',
+ Info = 'info',
+}
diff --git a/src/typings/contact/index.ts b/src/typings/contact/index.ts
new file mode 100644
index 0000000..fc753b9
--- /dev/null
+++ b/src/typings/contact/index.ts
@@ -0,0 +1,24 @@
+import type { Common } from '..'
+
+export enum FriendRequestStatus {
+ Pending = 'pending',
+ Accepted = 'accepted',
+ Rejected = 'rejected',
+}
+
+export interface Contact extends Common {
+ // 发起好友申请的用户 id
+ userId: string
+ // 被申请的用户 id
+ friendId: string
+ // 申请状态
+ status: FriendRequestStatus
+ // 申请理由
+ reason: string
+ // friend 给 user 的备注
+ friendRemark: string
+ // user 给 friend 的备注
+ userRemark: string
+ // 好友关系建立时间
+ establishAt: string
+}
diff --git a/src/typings/index.ts b/src/typings/index.ts
index d7f646c..3956478 100644
--- a/src/typings/index.ts
+++ b/src/typings/index.ts
@@ -1,3 +1,4 @@
export * from './common'
export * from './user'
export * from './chat'
+export * from './contact'