From 032882dbec1837b82941d58bc6551f5d52e961e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BC=96=E7=A0=81=E5=B7=A5=E7=A8=8B=E5=B8=88?= Date: Sun, 16 Aug 2026 02:19:05 +0800 Subject: [PATCH] =?UTF-8?q?feat(pc-client):=20=E9=80=9A=E8=AE=AF=E5=BD=95?= =?UTF-8?q?=E5=85=A5=E5=8F=A3=E6=95=B4=E6=94=B9=20+=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=90=8C=E4=BA=8B=E5=BC=B9=E7=AA=97=20+=20=E5=8F=91=E9=80=81?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E6=8F=90=E7=A4=BA=EF=BC=88=E8=A7=86=E8=A7=89?= =?UTF-8?q?=E8=A7=84=E8=8C=83=20v1=EF=BC=8CB-52=20=E6=96=BD=E5=B7=A5=203/3?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ContactSider:三个功能入口改为 20px 线性图标 + 36x36 浅蓝图标块 + 文字 (①新的同事②添加同事③我的群聊),行高 44,删掉 48px PNG 大图 - 新增 AddColleague 弹窗:工号 + 可编辑验证消息 + 五态(默认/校验失败/发送中/成功/失败) - 会话栏搜索框右侧新增「+」入口,两处打开同一个弹窗 - useSendMessage:空接收方明确提示、SDK 错误码映射中文提示 - 申请状态文案对齐规范:等待对方通过/已添加/已拒绝 - global.scss 补 --search-bg / --chat-bg token Co-authored-by: multica-agent --- pc-client/src/i18n/resources/en.json | 5 +- pc-client/src/i18n/resources/zh.json | 7 +- .../pages/chat/ConversationSider/index.tsx | 30 ++++-- .../queryChat/ChatFooter/useSendMessage.ts | 33 +++++- pc-client/src/pages/common/AddColleague.tsx | 102 ++++++++++++++++++ pc-client/src/pages/contact/ContactSider.tsx | 79 +++++++------- pc-client/src/styles/global.scss | 2 + 7 files changed, 203 insertions(+), 55 deletions(-) create mode 100644 pc-client/src/pages/common/AddColleague.tsx diff --git a/pc-client/src/i18n/resources/en.json b/pc-client/src/i18n/resources/en.json index 51a0877..af9151a 100644 --- a/pc-client/src/i18n/resources/en.json +++ b/pc-client/src/i18n/resources/en.json @@ -220,6 +220,7 @@ "latestChat": "Recent Chats", "myFriend": "My Friends", "myGroup": "My Groups", + "addColleague": "Add Colleague", "groupName": "Group Name", "groupAvatar": "Group Avatar", "groupMember": "Group Members", @@ -461,9 +462,9 @@ "custom": "Custom" }, "application": { - "agree": "Agree", + "agree": "Accept", "refuse": "Refuse", - "agreed": "Agreed", + "agreed": "Added", "refused": "Refused", "information": "Verification Information", "pending": "Pending Verification", diff --git a/pc-client/src/i18n/resources/zh.json b/pc-client/src/i18n/resources/zh.json index adcfba4..80bbebd 100644 --- a/pc-client/src/i18n/resources/zh.json +++ b/pc-client/src/i18n/resources/zh.json @@ -232,6 +232,7 @@ "latestChat": "最近聊天", "myFriend": "我的好友", "myGroup": "我的群组", + "addColleague": "添加同事", "groupName": "群名称", "groupAvatar": "群头像", "groupMember": "群成员", @@ -473,12 +474,12 @@ "custom": "自定义" }, "application": { - "agree": "同意", + "agree": "接受", "refuse": "拒绝", - "agreed": "已同意", + "agreed": "已添加", "refused": "已拒绝", "information": "验证信息", - "pending": "等待验证", + "pending": "等待对方通过", "applyToJoin": "申请加入", "applyToAdd": "申请添加", "applyToFriend": "申请添加您为好友" diff --git a/pc-client/src/pages/chat/ConversationSider/index.tsx b/pc-client/src/pages/chat/ConversationSider/index.tsx index d0687e1..86870c2 100644 --- a/pc-client/src/pages/chat/ConversationSider/index.tsx +++ b/pc-client/src/pages/chat/ConversationSider/index.tsx @@ -1,4 +1,4 @@ -import { SearchOutlined } from "@ant-design/icons"; +import { SearchOutlined, PlusOutlined } from "@ant-design/icons"; import { Button, Input, Spin } from "antd"; import { t } from "i18next"; import { useMemo, useRef, useState } from "react"; @@ -6,6 +6,7 @@ import { useNavigate, useParams } from "react-router-dom"; import { Virtuoso, VirtuosoHandle } from "react-virtuoso"; import FlexibleSider from "@/components/FlexibleSider"; +import AddColleague from "@/pages/common/AddColleague"; import { useConversationStore, useUserStore } from "@/store"; import ConversationItemComp from "./ConversationItem"; @@ -60,6 +61,7 @@ const ConversationSider = () => { const syncState = useUserStore((state) => state.syncState); const connectState = useUserStore((state) => state.connectState); const [keyword, setKeyword] = useState(""); + const [addColleagueOpen, setAddColleagueOpen] = useState(false); const virtuoso = useRef(null); const hasmore = useRef(true); const loading = useRef(false); @@ -151,16 +153,26 @@ const ConversationSider = () => { {connectState === "failed" && }
{t("placeholder.chat")}
- } - placeholder={t("placeholder.search")} - allowClear - value={keyword} - onChange={(e) => setKeyword(e.target.value)} - /> +
+ } + placeholder={t("placeholder.search")} + allowClear + value={keyword} + onChange={(e) => setKeyword(e.target.value)} + /> +
setAddColleagueOpen(true)} + > + +
+
{renderBody()} + setAddColleagueOpen(false)} /> ); }; diff --git a/pc-client/src/pages/chat/queryChat/ChatFooter/useSendMessage.ts b/pc-client/src/pages/chat/queryChat/ChatFooter/useSendMessage.ts index 4c34eeb..bfbf886 100644 --- a/pc-client/src/pages/chat/queryChat/ChatFooter/useSendMessage.ts +++ b/pc-client/src/pages/chat/queryChat/ChatFooter/useSendMessage.ts @@ -5,6 +5,7 @@ import { useCallback } from "react"; import { IMSDK } from "@/layout/MainContentWrap"; import { useConversationStore } from "@/store"; +import { feedbackToast } from "@/utils/common"; import { emit } from "@/utils/events"; import { pushNewMessage, updateOneMessage } from "../useHistoryMessageList"; @@ -14,6 +15,24 @@ export type SendMessageParams = Partial> & { needPush?: boolean; }; +const sendErrorTips = [ + { code: 1303, tip: "对方还不是你的同事,请先添加对方" }, + { code: 1302, tip: "对方已把你加入黑名单,无法发送" }, + { code: 1101, tip: "对方账号不存在" }, + { code: 1501, tip: "登录已过期,请重新登录" }, + { code: 1502, tip: "登录凭证无效,请重新登录" }, +]; + +/** 把 SDK 原始错误映射成中文提示 */ +const mapSendError = (error: unknown): string => { + const s = String(error ?? ""); + for (const { code, tip } of sendErrorTips) { + if (s.includes(String(code))) return tip; + } + if (/10000|10001|timeout|network|网络/i.test(s)) return "网络异常,请检查网络后重试"; + return "消息没发出去,请重试"; +}; + export function useSendMessage() { const sendMessage = useCallback( async ({ recvID, groupID, message, needPush }: SendMessageParams) => { @@ -30,9 +49,18 @@ export function useSendMessage() { emit("CHAT_LIST_SCROLL_TO_BOTTOM"); } + const finalRecvID = recvID ?? currentConversation?.userID ?? ""; + const finalGroupID = groupID ?? currentConversation?.groupID ?? ""; + // 单聊且接收方为空:会话对象异常,明确提示而不是静默失败 + if (!finalGroupID && !finalRecvID) { + updateOneMessage({ ...message, status: MessageStatus.Failed }); + feedbackToast({ msg: "请先添加对方为同事,再发起会话" }); + return; + } + const options = { - recvID: recvID ?? currentConversation?.userID ?? "", - groupID: groupID ?? currentConversation?.groupID ?? "", + recvID: finalRecvID, + groupID: finalGroupID, message, }; @@ -44,6 +72,7 @@ export function useSendMessage() { ...message, status: MessageStatus.Failed, }); + feedbackToast({ error, msg: mapSendError(error) }); } }, [], diff --git a/pc-client/src/pages/common/AddColleague.tsx b/pc-client/src/pages/common/AddColleague.tsx new file mode 100644 index 0000000..fd30589 --- /dev/null +++ b/pc-client/src/pages/common/AddColleague.tsx @@ -0,0 +1,102 @@ +import { IMSDK } from "@/layout/MainContentWrap"; +import { useContactStore } from "@/store"; +import { feedbackToast } from "@/utils/common"; +import { Input, Modal } from "antd"; +import { useEffect, useState } from "react"; + +interface IAddColleagueProps { + open: boolean; + onClose: () => void; +} + +/** + * 添加同事弹窗(视觉规范 v1 §4.2): + * 工号输入 + 可编辑验证消息 + 发送按钮,五态:默认/校验失败/发送中/成功/失败。 + * 成功后刷新申请数,2s 后关闭并提示「申请已发送,等对方通过」。 + */ +const AddColleague = ({ open, onClose }: IAddColleagueProps) => { + const [staffNo, setStaffNo] = useState(""); + const [verifyMsg, setVerifyMsg] = useState("你好,我想加你为同事"); + const [error, setError] = useState(""); + const [sending, setSending] = useState(false); + + useEffect(() => { + if (open) { + setStaffNo(""); + setVerifyMsg("你好,我想加你为同事"); + setError(""); + setSending(false); + } + }, [open]); + + const send = async () => { + const id = staffNo.trim(); + if (!id) { + setError("请输入对方工号"); + return; + } + setSending(true); + try { + await IMSDK.addFriend({ + toUserID: id, + reqMsg: verifyMsg.trim() || "你好,我想加你为同事", + }); + useContactStore.getState().getRecvFriendApplicationListByReq(); + feedbackToast({ msg: "申请已发送,等对方通过" }); + setTimeout(onClose, 500); + } catch (error) { + setError("发送失败,请确认工号正确、网络正常"); + feedbackToast({ error, msg: "发送失败,请确认工号正确、网络正常" }); + } finally { + setSending(false); + } + }; + + return ( + +
+
同事工号
+ { + setStaffNo(e.target.value); + if (error) setError(""); + }} + placeholder="请输入对方工号" + status={error ? "error" : undefined} + style={{ height: 36, borderRadius: 8, backgroundColor: "var(--search-bg)" }} + disabled={sending} + onPressEnter={send} + /> + {error &&
{error}
} +
验证消息(可不填)
+ setVerifyMsg(e.target.value)} + placeholder="你好,我想加你为同事" + autoSize={{ minRows: 2, maxRows: 4 }} + disabled={sending} + style={{ borderRadius: 8, backgroundColor: "var(--search-bg)" }} + /> + +
+
+ ); +}; + +export default AddColleague; diff --git a/pc-client/src/pages/contact/ContactSider.tsx b/pc-client/src/pages/contact/ContactSider.tsx index f53f695..0fd95c1 100644 --- a/pc-client/src/pages/contact/ContactSider.tsx +++ b/pc-client/src/pages/contact/ContactSider.tsx @@ -1,48 +1,48 @@ import { Badge } from "antd"; import clsx from "clsx"; +import { UserAddOutlined, UserOutlined, TeamOutlined } from "@ant-design/icons"; import i18n, { t } from "i18next"; import { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; -import group_notifications from "@/assets/images/contact/group_notifications.png"; -import my_friends from "@/assets/images/contact/my_friends.png"; -import my_groups from "@/assets/images/contact/my_groups.png"; -import new_friends from "@/assets/images/contact/new_friends.png"; import FlexibleSider from "@/components/FlexibleSider"; +import AddColleague from "@/pages/common/AddColleague"; import { useContactStore } from "@/store"; -const Links = [ +type LinkItem = { + label: string; + icon: React.ReactNode; + path?: string; + modal?: boolean; +}; + +// 通讯录功能入口:按视觉规范 v1 固定顺序 ①新的同事 ②添加同事 ③我的群聊, +// 全部用「20px 线性图标 + 36×36 浅蓝图标块 + 文字」,行高 44。 +const buildLinks = (): LinkItem[] => [ { label: t("placeholder.newFriends"), - icon: new_friends, + icon: , path: "/contact/newFriends", }, { - label: t("placeholder.groupNotification"), - icon: group_notifications, - path: "/contact/groupNotifications", - }, - { - label: t("placeholder.myFriend"), - icon: my_friends, - path: "/contact", + label: t("placeholder.addColleague"), + icon: , + modal: true, }, { label: t("placeholder.myGroup"), - icon: my_groups, + icon: , path: "/contact/myGroups", }, ]; i18n.on("languageChanged", () => { - Links[0].label = t("placeholder.newFriends"); - Links[1].label = t("placeholder.groupNotification"); - Links[2].label = t("placeholder.myFriend"); - Links[3].label = t("placeholder.myGroup"); + // buildLinks 每次渲染时都会重新读取文案,无需缓存更新 }); const ContactSider = () => { - const [selectIndex, setSelectIndex] = useState(2); + const [selectIndex, setSelectIndex] = useState(-1); + const [addColleagueOpen, setAddColleagueOpen] = useState(false); const unHandleFriendApplicationCount = useContactStore( (state) => state.unHandleFriendApplicationCount, ); @@ -50,16 +50,14 @@ const ContactSider = () => { (state) => state.unHandleGroupApplicationCount, ); const navigate = useNavigate(); + const Links = buildLinks(); useEffect(() => { if (location.hash.includes("/contact/newFriends")) { setSelectIndex(0); } - if (location.hash.includes("/contact/groupNotifications")) { - setSelectIndex(1); - } if (location.hash.includes("/contact/myGroups")) { - setSelectIndex(3); + setSelectIndex(2); } }, []); @@ -67,10 +65,16 @@ const ContactSider = () => { if (index === 0) { return unHandleFriendApplicationCount; } - if (index === 1) { - return unHandleGroupApplicationCount; + return index === 2 ? unHandleGroupApplicationCount : 0; + }; + + const onClick = (item: LinkItem, index: number) => { + if (item.modal) { + setAddColleagueOpen(true); + return; } - return 0; + setSelectIndex(index); + if (item.path) navigate(item.path); }; return ( @@ -86,32 +90,29 @@ const ContactSider = () => { {Links.map((item, index) => { return (
  • { - setSelectIndex(index); - navigate(String(item.path)); - }} + onClick={() => onClick(item, index)} > - {item.label} + + {item.icon} + -
    {item.label}
    +
    {item.label}
  • ); })} + setAddColleagueOpen(false)} /> ); }; export default ContactSider; + diff --git a/pc-client/src/styles/global.scss b/pc-client/src/styles/global.scss index 73b6f29..c5944c1 100644 --- a/pc-client/src/styles/global.scss +++ b/pc-client/src/styles/global.scss @@ -8,6 +8,8 @@ --top-search-bar: #3b87f5; --sub-text: #86909c; --gap-text: #eeeeee; + --search-bg: #f2f3f5; + --chat-bg: #f5f6f8; --warn-text: #f53f3f; --moment-text: #6085b1; --searchbar-height: 2.5rem;