新增 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
+18
View File
@@ -0,0 +1,18 @@
import { App } from "antd";
import type { MessageInstance } from "antd/es/message/interface";
import type { ModalStaticFunctions } from "antd/es/modal/confirm";
import type { NotificationInstance } from "antd/es/notification/interface";
let message: MessageInstance;
let notification: NotificationInstance;
let modal: Omit<ModalStaticFunctions, "warn">;
export default () => {
const staticFunction = App.useApp();
message = staticFunction.message;
modal = staticFunction.modal;
notification = staticFunction.notification;
return null;
};
export { message, modal, notification };
+44
View File
@@ -0,0 +1,44 @@
import { App as AntdApp, ConfigProvider, theme } from "antd";
import enUS from "antd/locale/en_US";
import zhCN from "antd/locale/zh_CN";
import { Suspense, useEffect } from "react";
import { QueryClient, QueryClientProvider } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";
import { RouterProvider } from "react-router-dom";
import AntdGlobalComp from "./AntdGlobalComp";
import router from "./routes";
import { useUserStore } from "./store";
function App() {
const locale = useUserStore((state) => state.appSettings.locale);
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
},
},
});
return (
<ConfigProvider
autoInsertSpaceInButton={false}
locale={locale === "zh-CN" ? zhCN : enUS}
theme={{
token: { colorPrimary: "#0073D9" },
}}
>
<QueryClientProvider client={queryClient}>
<Suspense fallback={<div>loading...</div>}>
<AntdApp>
<AntdGlobalComp />
<RouterProvider router={router} />
</AntdApp>
</Suspense>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</ConfigProvider>
);
}
export default App;
+14
View File
@@ -0,0 +1,14 @@
import { message } from "@/AntdGlobalComp";
import { ErrCodeMap } from "@/constants";
interface ErrorData {
errCode: number;
errMsg?: string;
}
export const errorHandle = (err: unknown) => {
const errData = err as ErrorData;
if (errData.errMsg) {
message.error(ErrCodeMap[errData.errCode] || errData.errMsg);
}
};
+31
View File
@@ -0,0 +1,31 @@
import axios from "axios";
import { getIMToken } from "@/utils/storage";
// RTC(音视频通话)令牌改由自研账号服务签发(原 OpenIM chat 服务 :10008 已下线)。
// 账号服务 /api/rtc_token 用登录拿到的 IM token 鉴权(Authorization: Bearer),
// 返回形状保持 { data: { serverUrl, token } }RtcCallModal 调用处无需改动。
export const getRtcConnectData = async (room: string, identity: string) => {
const imToken = await getIMToken();
if (!imToken) {
return Promise.reject(new Error("登录已过期,请重新登录"));
}
const { data: res } = await axios.post<{
code: number;
msg: string;
data: { token: string };
}>(
`${import.meta.env.VITE_ACCOUNT_URL}/api/rtc_token`,
{ room, identity },
{ headers: { Authorization: `Bearer ${imToken}` } },
);
if (res.code !== 0) {
return Promise.reject(new Error(res.msg || "获取通话令牌失败"));
}
return {
data: {
serverUrl: import.meta.env.VITE_LIVEKIT_URL as string,
token: res.data.token,
},
};
};
+89
View File
@@ -0,0 +1,89 @@
import type { MessageReceiveOptType } from "@openim/wasm-client-sdk";
import { IMSDK } from "@/layout/MainContentWrap";
// 原 OpenIM chat 业务服务(:10008)已下线,用户信息相关接口全部改走 OpenIM SDK。
// 以下导出保持原有函数签名和返回形状({ data: { users } } 等),调用处无需大改。
export interface BusinessUserInfo {
userID: string;
password: string;
account: string;
phoneNumber: string;
areaCode: string;
email: string;
nickname: string;
faceURL: string;
gender: number;
level: number;
birth: number;
allowAddFriend: BusinessAllowType;
allowBeep: BusinessAllowType;
allowVibration: BusinessAllowType;
globalRecvMsgOpt: MessageReceiveOptType;
}
export enum BusinessAllowType {
Allow = 1,
NotAllow = 2,
}
// 批量获取用户信息(原 chat 服务 /user/find/full
// SDK 只提供公开资料(userID/nickname/faceURL/ex),手机号、邮箱等原业务字段不再有值。
export const getBusinessUserInfo = async (userIDs: string[]) => {
const { data } = await IMSDK.getUsersInfo(userIDs);
return {
data: {
users: data as unknown as BusinessUserInfo[],
},
};
};
// 按工号精确查找用户(原 chat 服务 /user/search/full
export const searchBusinessUserInfo = async (keyword: string) => {
try {
const { data } = await IMSDK.getUsersInfo([keyword]);
return {
data: {
total: data.length,
users: data as unknown as BusinessUserInfo[],
},
};
} catch {
// 查不到(用户不存在等)统一按无结果处理,由调用处提示“未搜索到相关结果”
return {
data: {
total: 0,
users: [] as BusinessUserInfo[],
},
};
}
};
interface UpdateBusinessUserInfoParams {
email: string;
nickname: string;
faceURL: string;
gender: number;
birth: number;
allowAddFriend: number;
allowBeep: number;
allowVibration: number;
globalRecvMsgOpt: number;
}
// 更新个人资料(原 chat 服务 /user/update
// OpenIM 只支持同步昵称/头像等字段,邮箱、性别、生日等业务字段仅更新本地,不会落服务端。
export const updateBusinessUserInfo = async (
params: Partial<UpdateBusinessUserInfoParams>,
) => {
const imFields: { nickname?: string; faceURL?: string; globalRecvMsgOpt?: MessageReceiveOptType } =
{};
if (params.nickname !== undefined) imFields.nickname = params.nickname;
if (params.faceURL !== undefined) imFields.faceURL = params.faceURL;
if (params.globalRecvMsgOpt !== undefined) {
imFields.globalRecvMsgOpt = params.globalRecvMsgOpt as MessageReceiveOptType;
}
await IMSDK.setSelfInfo(imFields);
return { data: {} };
};
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 961 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 669 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 649 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 894 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 884 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 719 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1007 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 855 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 907 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 795 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 928 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 900 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 827 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 817 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 723 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 723 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 817 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 818 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 446 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 912 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 661 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 833 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 485 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 846 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 602 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 946 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 913 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 897 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 893 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 880 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Some files were not shown because too many files have changed in this diff Show More