新增 pc-client:畅联 PC 端工程(Electron+React,按确认效果图改造,B-59)

- 基于 openim-electron-demo 改造:工号+密码登录(自研账号服务)、会话列表/聊天窗/通讯录界面重做
- 文字/语音/文件/图片消息、文件拖拽发送、表情面板、一对一语音通话(LiveKit)
- RTC 令牌对接账号服务带鉴权版 /api/rtc_token(Bearer imToken + {room,identity})
- account-service: CORS Allow-Headers 补 authorization(浏览器/渲染进程跨域调 rtc_token 需要)
- 附本地 mock 账号服务(scripts/mock-account-server.js)与截图联调脚本
This commit is contained in:
KIMI
2026-08-09 09:11:06 +08:00
parent b95159f7eb
commit 7ce40e356f
284 changed files with 31599 additions and 7 deletions
@@ -0,0 +1,133 @@
import { RightOutlined } from "@ant-design/icons";
import { Button, Divider, Drawer } from "antd";
import { t } from "i18next";
import { forwardRef, ForwardRefRenderFunction, memo } from "react";
import { modal } from "@/AntdGlobalComp";
import OIMAvatar from "@/components/OIMAvatar";
import SettingRow from "@/components/SettingRow";
import { OverlayVisibleHandle, useOverlayVisible } from "@/hooks/useOverlayVisible";
import { IMSDK } from "@/layout/MainContentWrap";
import { useContactStore } from "@/store/contact";
import { feedbackToast } from "@/utils/common";
import { emit } from "@/utils/events";
import { useConversationStore } from "@/store";
// export interface SingleSettingProps {}
const SingleSetting: ForwardRefRenderFunction<OverlayVisibleHandle, unknown> = (
_,
ref,
) => {
const currentConversation = useConversationStore(
(state) => state.currentConversation,
);
const isBlack = useContactStore((state) => state.blackList).some(
(black) => currentConversation?.userID === black.userID,
);
const isFriend = useContactStore((state) => state.friendList).some(
(friend) => currentConversation?.userID === friend.userID,
);
const { isOverlayOpen, closeOverlay } = useOverlayVisible(ref);
const updateBlack = async () => {
if (!currentConversation) return;
const execFunc = async () => {
try {
isBlack
? await IMSDK.removeBlack(currentConversation?.userID)
: await IMSDK.addBlack({
toUserID: currentConversation?.userID,
});
} catch (error) {
feedbackToast({ error, msg: t("toast.updateBlackStateFailed") });
}
};
if (!isBlack) {
modal.confirm({
title: t("placeholder.moveBlacklist"),
content: (
<div className="flex items-baseline">
<div>{t("toast.confirmMoveBlacklist")}</div>
<span className="text-xs text-[var(--sub-text)]">
{t("placeholder.willFilterThisUserMessage")}
</span>
</div>
),
onOk: execFunc,
});
} else {
await execFunc();
}
};
const tryUnfriend = () => {
if (!currentConversation) return;
modal.confirm({
title: t("placeholder.unfriend"),
content: t("toast.confirmUnfriend"),
onOk: async () => {
try {
await IMSDK.deleteFriend(currentConversation.userID);
} catch (error) {
feedbackToast({ error, msg: t("toast.unfriendFailed") });
}
},
});
};
const openUserCard = () => {
emit("OPEN_USER_CARD", { userID: currentConversation?.userID });
};
return (
<Drawer
title={t("placeholder.setting")}
placement="right"
rootClassName="chat-drawer"
destroyOnClose
onClose={closeOverlay}
open={isOverlayOpen}
maskClassName="opacity-0"
maskMotion={{
visible: false,
}}
width={450}
getContainer={"#chat-container"}
>
<div
className="flex cursor-pointer items-center justify-between p-4"
onClick={openUserCard}
>
<div className="flex items-center">
<OIMAvatar
src={currentConversation?.faceURL}
text={currentConversation?.showName}
/>
<div className="ml-3">{currentConversation?.showName}</div>
</div>
<RightOutlined rev={undefined} />
</div>
<Divider className="m-0 border-4 border-[#F4F5F7]" />
<SettingRow
title={t("placeholder.moveBlacklist")}
value={isBlack}
tryChange={updateBlack}
/>
<Divider className="m-0 border-4 border-[#F4F5F7]" />
<div className="flex-1" />
{isFriend && (
<div className="flex w-full justify-center pb-3 pt-24">
<Button type="primary" danger onClick={tryUnfriend}>
{t("placeholder.unfriend")}
</Button>
</div>
)}
</Drawer>
);
};
export default memo(forwardRef(SingleSetting));