commit 3377b6072a1462d8cf87285143b029bac0afa1b0 Author: annali168290-ux Date: Fri Oct 9 22:49:53 2026 +0700 Import JC Web IM diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..240c3a3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +node_modules/ +.deploy/ +.DS_Store +npm-debug.log* +yarn-debug.log* +yarn-error.log* +*.local +.env +.env.* +SESSION_HANDOFF.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..78eadf3 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,3 @@ +# JC Web IM 开发记录 + +- 业务操作的成功、失败、保存结果提示,不要使用几秒后自动消失的 toast / popup。优先放在当前表单、按钮附近或弹窗内部,以常驻文字显示;在用户再次编辑、切换或关闭当前界面时再清除。 diff --git a/README.md b/README.md new file mode 100644 index 0000000..07b6dee --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +# 绿地信通 Web IM + +这是从零重写的 Web IM 前端,UI 和状态管理独立于原 `vue-chat`,底层通过授权的 WildFire Web SDK 连接既有 IM 服务。 + +## 当前工作范围 + +- 当前工作是在 `jc-web-im` 目录新写 Web 版 IM 客户端。 +- 不要默认回到仓库根目录老 `src/` 客户端里继续改,除非明确需要兼容或参考旧实现。 +- 中断恢复时优先阅读 `/var/www/im-web/SESSION_HANDOFF.md`。 + +## 服务地址 + +- App Server: `http://52.76.146.193:8888` +- IM route: `http://52.76.146.193:80/route` +- WebSocket: `ws://52.76.146.193:8083/ws` +- HTTPS/WSS 当前未启用为默认连接方式。 + +远端部署路径: + +- App Server: `/opt/app-server` +- IM Server: `/opt/im-server` + +## 运行 + +```bash +cd /var/www/im-web/jc-web-im +npm run serve +``` + +开发访问地址: + +```text +http://localhost:8023 +http://10.0.0.120:8023 +``` + +## 构建 + +```bash +npm run build +``` + +## 部署 + +生产部署请看 [WEB_DEPLOYMENT.md](./WEB_DEPLOYMENT.md)。当前 Web 版需要 Nginx 托管静态文件,并通过 PHP-FPM 运行 `dist/api/*.php` 代理接口。 + +## 连接方式 + +`src/services/imClient.js` 是唯一的 IM 适配层。上层 UI 只调用这里的方法: + +- `loginWithPassword` +- `loginWithCode` +- `connect` +- `getConversationList` +- `getMessages` +- `sendTextMessage` + +后续如果替换 SDK 或自研协议,只改这个适配层,不需要改 UI 组件。 diff --git a/WEB_DEPLOYMENT.md b/WEB_DEPLOYMENT.md new file mode 100644 index 0000000..43b9d39 --- /dev/null +++ b/WEB_DEPLOYMENT.md @@ -0,0 +1,284 @@ +# Web 版部署说明 + +本文档说明 `jc-web-im` 的生产部署方式。当前 Web 版由两部分组成: + +- Vue 构建后的静态文件,部署到 Web 根目录。 +- `dist/api/*.php` PHP 代理接口,负责安全调用本机 `im-server` / `app-server` 相关接口。 + +浏览器不能直接携带 `im-server` admin secret 访问 `18080`,所以联系人列表、备注名修改、消息发送等功能通过 PHP 代理完成。 + +## 目录 + +```text +/var/www/jc-im/jc-web-im +├── public/api/ # PHP 代理源码,构建时复制到 dist/api +├── public/config/ # 运行时配置 +├── src/ # Vue 前端源码 +├── deploy/ # Nginx 示例配置 +└── dist/ # npm run build 后的生产目录 +``` + +生产环境建议让 Nginx 的 `root` 指向: + +```text +/var/www/jc-im/jc-web-im/dist +``` + +## 依赖 + +服务器需要安装: + +- Nginx +- PHP-FPM +- PHP 扩展:`curl`、`mbstring` +- Node.js / npm,仅构建时需要 + +PHP 版本: + +- 最低建议:PHP 7.4 +- 推荐:PHP 8.1 / 8.2 / 8.3 + +当前 PHP 代理代码使用 `curl_*`、`random_int`、`Throwable`、`mb_strlen` 等能力,部署前请确认 `curl` 和 `mbstring` 扩展可用。 + +## 后端服务要求 + +Web 版依赖以下服务正常运行: + +```text +app-server: http://127.0.0.1:18888 +im-server HTTP/API: http://127.0.0.1:18000 +im-server admin: http://127.0.0.1:18080 +``` + +`im-server admin`、IM 数据库和应用数据库配置通过 PHP-FPM 环境变量提供: + +```text +public/api/im_web_bootstrap.php +``` + +需要配置: + +```text +IM_ADMIN_URL=http://127.0.0.1:18080 +IM_ADMIN_SECRET=请填写管理端密钥 +IM_DB_DSN=mysql:host=127.0.0.1;port=3306;dbname=wfchat;charset=utf8mb4 +IM_DB_USER=请填写数据库用户 +IM_DB_PASSWORD=请填写数据库密码 +IM_APP_DB_DSN=mysql:host=127.0.0.1;port=3306;dbname=appdata;charset=utf8mb4 +IM_APP_DB_USER=请填写应用数据库用户 +IM_APP_DB_PASSWORD=请填写应用数据库密码 +IM_DB_TIMEZONE=Asia/Bangkok +``` + +请在 PHP-FPM pool、容器 secret 或部署平台的环境变量中配置,禁止把真实密钥提交到仓库。 + +## 构建 + +在项目目录执行: + +```bash +cd /var/www/jc-im/jc-web-im +npm run build +``` + +构建完成后,生产文件位于: + +```text +/var/www/jc-im/jc-web-im/dist +``` + +## 运行时配置 + +运行时配置文件: + +```text +dist/config/runtime-config.js +``` + +示例: + +```js +window.JC_WEB_IM_CONFIG = { + appServer: 'http://your-domain:18888', + registerConfigApi: 'http://your-domain:8024/api/register-config', + imServerHost: 'your-domain', + imRoutePort: 18000, + useWss: false, + wsPath: '/ws', + sdkMode: 'custom-ws' +}; +``` + +常用字段说明: + +| 字段 | 说明 | +| --- | --- | +| `appServer` | Web 登录、验证码等 App Server 地址 | +| `registerConfigApi` | 注册配置接口地址 | +| `imServerHost` | IM HTTP/API 和 WebSocket 使用的主机名 | +| `imRoutePort` | IM route/API 端口,当前为 `18000` | +| `useWss` | 是否使用 WSS | +| `wsPath` | WebSocket 路径 | +| `sdkMode` | 当前使用 `custom-ws` | + +联系人相关 PHP 代理默认使用同源地址: + +```text +/api/contacts.php +/api/update_alias.php +/api/add_contact.php +/api/send_message.php +/api/messages.php +``` + +通常不需要在 `runtime-config.js` 里单独配置这些地址。 + +## Nginx 示例 + +根据实际 PHP-FPM socket 调整 `fastcgi_pass`,例如 PHP 8.3: + +```nginx +server { + listen 8023; + listen [::]:8023; + server_name your-domain _; + + root /var/www/jc-im/jc-web-im/dist; + index index.html; + client_max_body_size 100m; + + location = / { + try_files /index.html =404; + expires -1; + add_header Cache-Control "no-store"; + } + + location = /index.html { + expires -1; + add_header Cache-Control "no-store"; + } + + location = /config/runtime-config.js { + expires -1; + add_header Cache-Control "no-store"; + } + + location ~ ^/api/.+\.php$ { + include snippets/fastcgi-php.conf; + fastcgi_param PHP_ADMIN_VALUE "open_basedir=/var/www/jc-im/jc-web-im/dist/:/tmp/"; + fastcgi_pass unix:/run/php/php8.3-fpm.sock; + expires -1; + add_header Cache-Control "no-store"; + } + + location / { + etag on; + try_files $uri $uri/ /index.html; + expires 12h; + } +} +``` + +启用配置后检查并重载: + +```bash +sudo nginx -t +sudo systemctl reload nginx +``` + +## 防火墙建议 + +生产环境建议: + +- 对外开放 Web 端口,例如 `80` / `443` / `8023`。 +- `18888` 如需浏览器直接访问 app-server,需要对外开放。 +- `18000` 如 WebSocket/route 需要浏览器直连,需要对外开放。 +- `18080` admin 端口不要对公网开放,只允许本机 PHP 代理访问。 + +## 验证 + +检查 PHP 语法: + +```bash +php -l /var/www/jc-im/jc-web-im/dist/api/contacts.php +php -l /var/www/jc-im/jc-web-im/dist/api/update_alias.php +``` + +检查 Web 页面: + +```bash +curl -I http://127.0.0.1:8023/ +``` + +检查联系人接口: + +```bash +curl -s -H 'Content-Type: application/json' \ + --data '{"userId":"你的用户ID"}' \ + http://127.0.0.1:8023/api/contacts.php +``` + +成功时返回: + +```json +{ + "code": 0, + "message": "success", + "result": { + "contacts": [] + } +} +``` + +检查备注名修改接口: + +```bash +curl -s -H 'Content-Type: application/json' \ + --data '{"userId":"你的用户ID","targetId":"联系人ID","alias":"测试备注"}' \ + http://127.0.0.1:8023/api/update_alias.php +``` + +成功时返回: + +```json +{ + "code": 0, + "message": "success", + "result": { + "contact": { + "uid": "联系人ID", + "alias": "测试备注" + } + } +} +``` + +## 常见问题 + +### 页面能打开,但联系人加载失败 + +优先检查: + +- PHP-FPM 是否运行。 +- Nginx 的 PHP location 是否命中 `/api/*.php`。 +- `curl` 扩展是否启用。 +- `public/api/im_web_bootstrap.php` 中 `IM_ADMIN_URL` 和 `IM_ADMIN_SECRET` 是否匹配当前 `im-server`。 +- `im-server` 的 `18080` admin 接口是否在本机可访问。 + +### 修改备注名无效 + +检查: + +- `dist/api/update_alias.php` 是否存在。 +- Nginx 是否允许执行 `dist/api/*.php`。 +- `im-server` 是否提供 `/admin/friend/set_alias`。 +- 当前登录用户和目标联系人是否确实是好友关系。 + +### 部署后仍是旧前端 + +检查: + +- Nginx `root` 是否指向新的 `dist` 目录。 +- 是否执行过 `npm run build`。 +- 浏览器是否缓存了旧资源。`index.html` 和 `runtime-config.js` 应配置为 `no-store`。 + diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000..ffa9add --- /dev/null +++ b/babel.config.js @@ -0,0 +1,3 @@ +module.exports = { + presets: ['@vue/cli-plugin-babel/preset'] +}; diff --git a/deploy/im-web-8023.nginx.conf b/deploy/im-web-8023.nginx.conf new file mode 100644 index 0000000..0f73a5b --- /dev/null +++ b/deploy/im-web-8023.nginx.conf @@ -0,0 +1,33 @@ +server { + listen 8023; + listen [::]:8023; + server_name winchen.ezpay.asia 10.0.0.120 _; + + root /var/www/im-web; + index index.html; + client_max_body_size 100m; + + location = / { + try_files /index.html =404; + expires -1; + add_header Cache-Control "no-store"; + } + + location = /index.html { + expires -1; + add_header Cache-Control "no-store"; + } + + location ~ ^/api/.+\.php$ { + include snippets/fastcgi-php.conf; + fastcgi_param PHP_ADMIN_VALUE "open_basedir=/var/www/im-web/:/tmp/"; + fastcgi_pass unix:/run/php/php8.3-fpm.sock; + expires -1; + } + + location / { + etag on; + try_files $uri $uri/ /index.html; + expires 12h; + } +} diff --git a/dist/api/add_contact.php b/dist/api/add_contact.php new file mode 100644 index 0000000..586f066 --- /dev/null +++ b/dist/api/add_contact.php @@ -0,0 +1,95 @@ + $uid, + 'name' => (string)($user['name'] ?? $uid), + 'displayName' => (string)($user['displayName'] ?? $user['name'] ?? $uid), + 'mobile' => (string)($user['mobile'] ?? ''), + 'portrait' => (string)($user['portrait'] ?? ''), + 'alias' => '', + ]; +} + +try { + $target = tryImUserInfo(['userId' => $keyword]); + if (!$target && preg_match('/^\+?\d{5,32}$/', $keyword)) { + $target = tryImUserInfo(['mobile' => $keyword]); + } + if (!$target) { + $target = tryImUserInfo(['name' => $keyword]); + } + + if (!$target) { + respond(404, '没有找到该用户'); + } + + $contact = normalizeUserInfo($target); + if ($contact['uid'] === '') { + respond(404, '没有找到该用户'); + } + if ($contact['uid'] === $userId) { + respond(400, '不能添加自己为联系人'); + } + + $friendResult = requireImSuccess( + imAdminPost('/admin/friend/list', ['userId' => $userId]), + '好友列表检查失败' + ); + $friendIds = array_values(array_filter($friendResult['list'] ?? [], 'is_string')); + if (in_array($contact['uid'], $friendIds, true)) { + respond(409, '该用户已经是联系人', ['contact' => $contact]); + } + + requireImSuccess( + imAdminPost('/admin/friend/status', [ + 'userId' => $userId, + 'friendUid' => $contact['uid'], + 'status' => 0, + ]), + '添加联系人失败' + ); + requireImSuccess( + imAdminPost('/admin/friend/status', [ + 'userId' => $contact['uid'], + 'friendUid' => $userId, + 'status' => 0, + ]), + '添加联系人失败' + ); + + respond(0, 'success', ['contact' => $contact, 'reason' => $reason]); +} catch (Throwable $error) { + respond(500, $error->getMessage() ?: '添加联系人服务暂不可用'); +} diff --git a/dist/api/contacts.php b/dist/api/contacts.php new file mode 100644 index 0000000..7dee6d9 --- /dev/null +++ b/dist/api/contacts.php @@ -0,0 +1,71 @@ + $userId]), + '联系人加载失败' + ); + $friendIds = array_values(array_unique(array_filter(array_map(static function ($uid): string { + return trim((string)$uid); + }, $friendResult['list'] ?? [])))); + if (!$friendIds) { + respond(0, 'success', ['contacts' => []]); + } + + $usersResult = requireImSuccess( + imAdminPost('/admin/user/batch_get_infos', ['list' => $friendIds]), + '联系人资料加载失败' + ); + try { + ensureWebImAppTables(); + $remarkMap = fetchWebUserRemarkMap(imAppDb(), $userId, $friendIds); + } catch (Throwable) { + $remarkMap = []; + } + + $contactsByUid = []; + foreach (array_values(array_filter($usersResult['userInfos'] ?? [], 'is_array')) as $user) { + $uid = (string)($user['userId'] ?? ''); + if ($uid === '' || isset($contactsByUid[$uid])) { + continue; + } + $alias = ''; + try { + $aliasResult = requireImSuccess( + imAdminPost('/admin/friend/get_alias', ['operator' => $userId, 'targetId' => $uid]), + '联系人备注加载失败' + ); + $alias = trim((string)($aliasResult['alias'] ?? '')); + } catch (Throwable $error) { + $alias = ''; + } + if ($alias === '') { + $alias = $remarkMap[$uid] ?? ''; + } + $contactsByUid[$uid] = [ + 'uid' => $uid, + 'name' => (string)($user['name'] ?? $uid), + 'displayName' => (string)($user['displayName'] ?? $user['name'] ?? $uid), + 'mobile' => (string)($user['mobile'] ?? ''), + 'portrait' => (string)($user['portrait'] ?? ''), + 'address' => (string)($user['address'] ?? ''), + 'alias' => $alias, + ]; + } + + respond(0, 'success', ['contacts' => array_values($contactsByUid)]); +} catch (Throwable $error) { + respond(500, $error->getMessage() ?: '联系人服务暂不可用'); +} diff --git a/dist/api/conversation_actions.php b/dist/api/conversation_actions.php new file mode 100644 index 0000000..299cc7e --- /dev/null +++ b/dist/api/conversation_actions.php @@ -0,0 +1,990 @@ +prepare( + 'select `_uid` from `t_user` where `_uid` = ? or `_mobile` = ? or `_name` = ? limit 1' + ); + $statement->execute([$value, $value, $value]); + $uid = $statement->fetchColumn(); + if (is_string($uid) && $uid !== '') { + $resolved[] = $uid; + } + } + + return array_values(array_unique($resolved)); +} + +function webGroupConversation(array $group, string $role = 'member'): array +{ + $name = (string)($group['name'] ?? $group['_name'] ?? $group['group_id'] ?? $group['_gid'] ?? ''); + $groupId = (string)($group['group_id'] ?? $group['_gid'] ?? ''); + $category = (string)($group['category'] ?? 'group'); + $title = $name !== '' ? $name : $groupId; + $updatedAt = (int)($group['updated_at_ms'] ?? $group['_dt'] ?? webImNowMs()); + + return [ + 'id' => 'group-' . $groupId, + 'type' => $category === 'channel' ? 'channel' : 'group', + 'target' => $groupId, + 'title' => $title, + 'subtitle' => $category === 'channel' ? '公告群' : '群组', + 'unread' => 0, + 'avatarText' => mb_strtoupper(mb_substr($title, 0, 1, 'UTF-8'), 'UTF-8'), + 'updatedAt' => $updatedAt, + 'role' => $role, + 'raw' => [ + 'type' => 'group', + 'target' => $groupId, + 'line' => 0, + 'category' => $category, + 'role' => $role, + ], + ]; +} + +function fetchWebGroup(PDO $db, string $groupKey): ?array +{ + $statement = $db->prepare( + 'select * from `t_web_groups`' + . ' where `deleted` = 0 and (`group_id` = ? or `name` = ?)' + . ' order by (`group_id` = ?) desc limit 1' + ); + $statement->execute([$groupKey, $groupKey, $groupKey]); + $group = $statement->fetch(); + + return is_array($group) ? $group : null; +} + +function fetchWebGroupRole(PDO $db, string $groupId, string $userId): string +{ + $statement = $db->prepare( + 'select `role` from `t_web_group_members`' + . ' where `group_id` = ? and `user_id` = ? and `status` = 0 limit 1' + ); + $statement->execute([$groupId, $userId]); + $role = $statement->fetchColumn(); + + return is_string($role) && $role !== '' ? $role : 'member'; +} + +function fetchWebGroupMember(PDO $db, string $groupId, string $userId): array +{ + $statement = $db->prepare( + 'select * from `t_web_group_members`' + . ' where `group_id` = ? and `user_id` = ? and `status` = 0 limit 1' + ); + $statement->execute([$groupId, $userId]); + $member = $statement->fetch(); + + return is_array($member) ? $member : []; +} + +function requireWebGroupMember(PDO $db, string $groupId, string $userId): array +{ + $member = fetchWebGroupMember($db, $groupId, $userId); + if (!$member) { + respond(403, '你不在该群组中'); + } + + return $member; +} + +function requireWebGroupRole(PDO $db, string $groupId, string $userId, array $roles): array +{ + $member = requireWebGroupMember($db, $groupId, $userId); + if (!in_array((string)$member['role'], $roles, true)) { + respond(403, '没有群组管理权限'); + } + + return $member; +} + +function upsertWebGroupMember(PDO $db, string $groupId, string $userId, string $role): void +{ + $now = webImNowMs(); + $statement = $db->prepare( + 'insert into `t_web_group_members`' + . ' (`group_id`, `user_id`, `role`, `joined_at_ms`, `updated_at_ms`, `status`)' + . ' values (?, ?, ?, ?, ?, 0)' + . ' on duplicate key update' + . ' `role` = if(`role` = "owner", `role`, values(`role`)),' + . ' `updated_at_ms` = values(`updated_at_ms`),' + . ' `status` = 0' + ); + $statement->execute([$groupId, $userId, $role, $now, $now]); +} + +function createNativeGroup(string $operator, string $groupId, string $name, string $category, array $members): array +{ + $nativeMembers = []; + foreach ($members as $memberId) { + $nativeMembers[] = [ + 'member_id' => $memberId, + 'type' => $memberId === $operator ? 2 : 0, + ]; + } + + return requireImSuccess(imAdminPost('/admin/group/create', [ + 'operator' => $operator, + 'group' => [ + 'group_info' => [ + 'target_id' => $groupId, + 'name' => $name, + 'owner' => $operator, + 'type' => 0, + 'mute' => $category === 'channel' ? 1 : 0, + 'join_type' => 0, + 'searchable' => 1, + 'history_message' => 1, + 'extra' => json_encode(['webCategory' => $category], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), + ], + 'members' => $nativeMembers, + ], + 'to_lines' => [0], + ]), '原生群组创建失败'); +} + +function addNativeGroupMember(string $operator, string $groupId, string $userId): void +{ + requireImSuccess(imAdminPost('/admin/group/member/add', [ + 'operator' => $operator, + 'group_id' => $groupId, + 'members' => [ + ['member_id' => $userId, 'type' => 0], + ], + 'to_lines' => [0], + ]), '加入原生群组失败'); +} + +function removeNativeGroupMember(string $operator, string $groupId, array $userIds): void +{ + if (!$userIds) { + return; + } + + requireImSuccess(imAdminPost('/admin/group/member/del', [ + 'operator' => $operator, + 'group_id' => $groupId, + 'members' => array_values($userIds), + 'to_lines' => [0], + ]), '移除原生成员失败'); +} + +function dismissNativeGroup(string $operator, string $groupId): void +{ + requireImSuccess(imAdminPost('/admin/group/del', [ + 'operator' => $operator, + 'group_id' => $groupId, + 'to_lines' => [0], + ]), '解散原生群组失败'); +} + +function transferNativeGroup(string $operator, string $groupId, string $newOwner): void +{ + requireImSuccess(imAdminPost('/admin/group/transfer', [ + 'operator' => $operator, + 'group_id' => $groupId, + 'new_owner' => $newOwner, + 'to_lines' => [0], + ]), '转让原生群组失败'); +} + +function sendWebGroupWelcomeMessage(PDO $appDb, string $groupId, string $senderId, string $welcomeText): void +{ + $text = trim($welcomeText); + if ($text === '') { + return; + } + + $now = webImNowMs(); + $messageUid = webImMessageUid(); + $statement = $appDb->prepare( + 'insert into `t_web_group_messages`' + . ' (`message_uid`, `group_id`, `sender_uid`, `text`, `created_at_ms`, `edited_at_ms`, `recalled`)' + . ' values (?, ?, ?, ?, ?, 0, 0)' + ); + $statement->execute([$messageUid, $groupId, $senderId, $text, $now]); + $update = $appDb->prepare('update `t_web_groups` set `updated_at_ms` = ? where `group_id` = ?'); + $update->execute([$now, $groupId]); + + try { + requireImSuccess(imAdminPost('/admin/message/send', [ + 'sender' => $senderId, + 'conv' => [ + 'type' => 1, + 'target' => $groupId, + 'line' => 0, + ], + 'payload' => [ + 'type' => 1, + 'searchableContent' => $text, + 'pushContent' => $text, + 'content' => $text, + 'persistFlag' => 3, + ], + 'userMessage' => true, + ]), '欢迎词发送失败'); + } catch (Throwable) { + } +} + +function createWebGroupAction(PDO $imDb, PDO $appDb, string $userId, array $payload, string $category): array +{ + $name = trim((string)($payload['name'] ?? '')); + $announcement = trim((string)($payload['announcement'] ?? '')); + $welcomeText = trim((string)($payload['welcomeText'] ?? '')); + if ($name === '') { + respond(400, $category === 'channel' ? '请输入公告群名称' : '请输入群组名称'); + } + + $memberIds = resolveWebUserIds($imDb, webActionList($payload['memberIds'] ?? [])); + $memberIds[] = $userId; + $memberIds = array_values(array_unique(array_filter($memberIds))); + if ($category === 'group' && count($memberIds) < 2) { + respond(400, '请选择至少一位联系人'); + } + + $groupId = 'webg_' . bin2hex(random_bytes(8)); + $now = webImNowMs(); + $nativeStatus = 'pending'; + $nativeGroupId = ''; + $nativeError = ''; + + try { + $nativeResult = createNativeGroup($userId, $groupId, $name, $category, $memberIds); + $nativeGroupId = (string)($nativeResult['group_id'] ?? $nativeResult['groupId'] ?? $groupId); + $nativeStatus = 'ok'; + } catch (Throwable $error) { + $nativeStatus = 'fallback'; + $nativeError = mb_substr($error->getMessage() ?: '原生群组创建失败', 0, 255, 'UTF-8'); + } + + $statement = $appDb->prepare( + 'insert into `t_web_groups`' + . ' (`group_id`, `category`, `name`, `portrait`, `owner_uid`, `join_mode`, `announcement`,' + . ' `welcome_text`, `native_group_id`, `native_status`, `native_error`, `created_by`, `created_at_ms`, `updated_at_ms`, `deleted`)' + . ' values (?, ?, ?, "", ?, "open", ?, ?, ?, ?, ?, ?, ?, ?, 0)' + ); + $statement->execute([ + $groupId, + $category, + $name, + $userId, + $announcement, + $welcomeText, + $nativeGroupId, + $nativeStatus, + $nativeError, + $userId, + $now, + $now, + ]); + + foreach ($memberIds as $memberId) { + upsertWebGroupMember($appDb, $groupId, $memberId, $memberId === $userId ? 'owner' : 'member'); + } + sendWebGroupWelcomeMessage($appDb, $groupId, $userId, $welcomeText); + + $group = fetchWebGroup($appDb, $groupId); + return [ + 'group' => $group, + 'conversation' => webGroupConversation($group ?: [ + 'group_id' => $groupId, + 'category' => $category, + 'name' => $name, + 'updated_at_ms' => $now, + ], 'owner'), + 'nativeStatus' => $nativeStatus, + 'nativeError' => $nativeError, + ]; +} + +function fetchWebUserMap(PDO $imDb, array $userIds, string $ownerId = ''): array +{ + $userIds = webActionList($userIds); + if (!$userIds) { + return []; + } + + $statement = $imDb->prepare( + 'select `_uid`, `_name`, `_display_name`, `_mobile`, `_portrait` from `t_user`' + . ' where `_uid` in (' . appPlaceholders($userIds) . ')' + ); + $statement->execute($userIds); + + $users = []; + foreach ($statement->fetchAll() as $row) { + $uid = (string)$row['_uid']; + $displayName = trim((string)($row['_display_name'] ?? '')) + ?: trim((string)($row['_name'] ?? '')) + ?: trim((string)($row['_mobile'] ?? '')) + ?: $uid; + $users[$uid] = [ + 'uid' => $uid, + 'name' => (string)($row['_name'] ?? ''), + 'displayName' => $displayName, + 'mobile' => (string)($row['_mobile'] ?? ''), + 'portrait' => (string)($row['_portrait'] ?? ''), + 'contactAlias' => '', + 'friendAlias' => '', + 'remarkAlias' => '', + 'isContact' => false, + ]; + } + + if ($ownerId !== '') { + $aliasStatement = $imDb->prepare( + 'select `_friend_uid`, `_alias` from `t_friend` where `_uid` = ?' + . ' and `_friend_uid` in (' . appPlaceholders($userIds) . ')' + ); + $aliasStatement->execute(array_merge([$ownerId], $userIds)); + foreach ($aliasStatement->fetchAll() as $row) { + $friendUid = (string)$row['_friend_uid']; + if (!isset($users[$friendUid])) { + continue; + } + $users[$friendUid]['isContact'] = true; + $users[$friendUid]['friendAlias'] = trim((string)($row['_alias'] ?? '')); + } + + $remarkMap = fetchWebUserRemarkMap(imAppDb(), $ownerId, $userIds); + foreach ($remarkMap as $targetUid => $alias) { + if (!isset($users[$targetUid])) { + continue; + } + $users[$targetUid]['remarkAlias'] = $alias; + } + + foreach ($users as $uid => $user) { + $friendAlias = trim((string)($user['friendAlias'] ?? '')); + $remarkAlias = trim((string)($user['remarkAlias'] ?? '')); + $users[$uid]['contactAlias'] = $friendAlias !== '' ? $friendAlias : $remarkAlias; + } + } + + return $users; +} + +function currentMuteSetting(PDO $appDb, string $groupId): array +{ + $statement = $appDb->prepare('select * from `t_web_group_mute_rules` where `group_id` = ? limit 1'); + $statement->execute([$groupId]); + $row = $statement->fetch(); + if (!is_array($row)) { + return [ + 'mode' => 'off', + 'scheduleType' => 'once', + 'startsAtMs' => 0, + 'endsAtMs' => 0, + 'dailyStart' => '', + 'dailyEnd' => '', + 'enabled' => false, + ]; + } + + return [ + 'mode' => (string)$row['mode'], + 'scheduleType' => (string)$row['schedule_type'], + 'startsAtMs' => (int)$row['starts_at_ms'], + 'endsAtMs' => (int)$row['ends_at_ms'], + 'dailyStart' => (string)$row['daily_start'], + 'dailyEnd' => (string)$row['daily_end'], + 'enabled' => (bool)$row['enabled'], + ]; +} + +function groupDetailAction(PDO $imDb, PDO $appDb, string $userId, string $groupId): array +{ + $group = fetchWebGroup($appDb, $groupId); + if (!$group) { + respond(404, '没有找到该群组'); + } + $member = fetchWebGroupMember($appDb, $groupId, $userId); + if (!$member) { + respond(403, '你不在该群组中'); + } + + $ownerId = (string)$group['owner_uid']; + $users = fetchWebUserMap($imDb, [$ownerId, $userId], $userId); + $owner = $users[$ownerId] ?? ['uid' => $ownerId, 'displayName' => $ownerId, 'contactAlias' => '']; + $me = $users[$userId] ?? ['uid' => $userId, 'displayName' => $userId]; + $myAlias = (string)($member['alias'] ?? ''); + + return [ + 'group' => [ + 'groupId' => (string)$group['group_id'], + 'name' => (string)$group['name'], + 'category' => (string)$group['category'], + 'announcement' => (string)($group['announcement'] ?? ''), + 'welcomeText' => (string)($group['welcome_text'] ?? ''), + 'ownerUid' => $ownerId, + 'ownerName' => (string)($owner['contactAlias'] ?? '') !== '' ? (string)$owner['contactAlias'] : (string)$owner['displayName'], + 'myRole' => (string)$member['role'], + 'myAlias' => $myAlias, + 'myDisplayName' => $myAlias !== '' ? $myAlias : (string)$me['displayName'], + 'muteSetting' => currentMuteSetting($appDb, (string)$group['group_id']), + ], + ]; +} + +function groupMembersAction(PDO $imDb, PDO $appDb, string $userId, string $groupId): array +{ + if (!fetchWebGroupMember($appDb, $groupId, $userId)) { + respond(403, '你不在该群组中'); + } + + $statement = $appDb->prepare( + 'select `user_id`, `role`, `alias`, `joined_at_ms` from `t_web_group_members`' + . ' where `group_id` = ? and `status` = 0 order by (`role` = "owner") desc, `joined_at_ms` asc' + ); + $statement->execute([$groupId]); + $members = $statement->fetchAll(); + $users = fetchWebUserMap($imDb, array_column($members, 'user_id'), $userId); + + return [ + 'members' => array_map(static function (array $member) use ($users): array { + $uid = (string)$member['user_id']; + $user = $users[$uid] ?? [ + 'uid' => $uid, + 'displayName' => $uid, + 'mobile' => '', + 'portrait' => '', + 'contactAlias' => '', + 'friendAlias' => '', + 'remarkAlias' => '', + 'isContact' => false, + ]; + $alias = trim((string)($member['alias'] ?? '')); + $contactAlias = trim((string)($user['contactAlias'] ?? '')); + return [ + 'uid' => $uid, + 'role' => (string)$member['role'], + 'alias' => $alias, + 'contactAlias' => $contactAlias, + 'friendAlias' => trim((string)($user['friendAlias'] ?? '')), + 'remarkAlias' => trim((string)($user['remarkAlias'] ?? '')), + 'isContact' => (bool)($user['isContact'] ?? false), + 'displayName' => $contactAlias !== '' ? $contactAlias : ($alias !== '' ? $alias : (string)$user['displayName']), + 'rawDisplayName' => (string)$user['displayName'], + 'groupDisplayName' => $alias, + 'mobile' => (string)($user['mobile'] ?? ''), + 'portrait' => (string)($user['portrait'] ?? ''), + ]; + }, $members), + ]; +} + +function updateGroupAliasAction(PDO $appDb, string $userId, string $groupId, string $alias): array +{ + if (mb_strlen($alias, 'UTF-8') > 64) { + respond(400, '本群昵称过长'); + } + if (!fetchWebGroupMember($appDb, $groupId, $userId)) { + respond(403, '你不在该群组中'); + } + + $now = webImNowMs(); + $statement = $appDb->prepare( + 'update `t_web_group_members` set `alias` = ?, `updated_at_ms` = ?' + . ' where `group_id` = ? and `user_id` = ? and `status` = 0' + ); + $statement->execute([$alias, $now, $groupId, $userId]); + + return ['alias' => $alias]; +} + +function leaveGroupAction(PDO $appDb, string $userId, string $groupId): array +{ + $group = fetchWebGroup($appDb, $groupId); + if (!$group) { + respond(404, '没有找到该群组'); + } + $member = fetchWebGroupMember($appDb, $groupId, $userId); + if (!$member) { + respond(403, '你不在该群组中'); + } + + $now = webImNowMs(); + $statement = $appDb->prepare( + 'update `t_web_group_members` set `status` = 1, `updated_at_ms` = ?' + . ' where `group_id` = ? and `user_id` = ?' + ); + $statement->execute([$now, $groupId, $userId]); + $groupUpdate = $appDb->prepare('update `t_web_groups` set `updated_at_ms` = ? where `group_id` = ?'); + $groupUpdate->execute([$now, $groupId]); + + try { + requireImSuccess(imAdminPost('/admin/group/member/quit', [ + 'operator' => $userId, + 'group_id' => $groupId, + 'to_lines' => [0], + ]), '退出原生群组失败'); + } catch (Throwable) { + } + + return ['groupId' => $groupId]; +} + +function dismissGroupAction(PDO $appDb, string $userId, string $groupId): array +{ + $group = fetchWebGroup($appDb, $groupId); + if (!$group) { + respond(404, '没有找到该群组'); + } + requireWebGroupRole($appDb, $groupId, $userId, ['owner']); + + $now = webImNowMs(); + $update = $appDb->prepare('update `t_web_groups` set `deleted` = 1, `updated_at_ms` = ? where `group_id` = ?'); + $update->execute([$now, $groupId]); + $memberUpdate = $appDb->prepare('update `t_web_group_members` set `status` = 1, `updated_at_ms` = ? where `group_id` = ?'); + $memberUpdate->execute([$now, $groupId]); + + try { + dismissNativeGroup($userId, $groupId); + } catch (Throwable) { + } + + return ['groupId' => $groupId]; +} + +function transferGroupAction(PDO $appDb, string $userId, string $groupId, string $targetUserId): array +{ + if ($targetUserId === '' || $targetUserId === $userId) { + respond(400, '请选择新的群主'); + } + $group = fetchWebGroup($appDb, $groupId); + if (!$group) { + respond(404, '没有找到该群组'); + } + requireWebGroupRole($appDb, $groupId, $userId, ['owner']); + $target = requireWebGroupMember($appDb, $groupId, $targetUserId); + if ((string)$target['role'] === 'owner') { + respond(400, '该成员已经是群主'); + } + + $now = webImNowMs(); + $appDb->beginTransaction(); + try { + $groupUpdate = $appDb->prepare('update `t_web_groups` set `owner_uid` = ?, `updated_at_ms` = ? where `group_id` = ?'); + $groupUpdate->execute([$targetUserId, $now, $groupId]); + $oldOwnerUpdate = $appDb->prepare( + 'update `t_web_group_members` set `role` = "member", `updated_at_ms` = ?' + . ' where `group_id` = ? and `user_id` = ?' + ); + $oldOwnerUpdate->execute([$now, $groupId, $userId]); + $newOwnerUpdate = $appDb->prepare( + 'update `t_web_group_members` set `role` = "owner", `updated_at_ms` = ?' + . ' where `group_id` = ? and `user_id` = ?' + ); + $newOwnerUpdate->execute([$now, $groupId, $targetUserId]); + $appDb->commit(); + } catch (Throwable $error) { + $appDb->rollBack(); + throw $error; + } + + try { + transferNativeGroup($userId, $groupId, $targetUserId); + } catch (Throwable) { + } + + return ['groupId' => $groupId, 'newOwner' => $targetUserId]; +} + +function setGroupAdminsAction(PDO $appDb, string $userId, string $groupId, array $adminIds): array +{ + requireWebGroupRole($appDb, $groupId, $userId, ['owner']); + $adminIds = array_values(array_unique(array_filter(array_map('strval', $adminIds), static fn(string $uid): bool => $uid !== $userId))); + $now = webImNowMs(); + + $reset = $appDb->prepare( + 'update `t_web_group_members` set `role` = "member", `updated_at_ms` = ?' + . ' where `group_id` = ? and `role` = "admin" and `status` = 0' + ); + $reset->execute([$now, $groupId]); + + if ($adminIds) { + $update = $appDb->prepare( + 'update `t_web_group_members` set `role` = "admin", `updated_at_ms` = ?' + . ' where `group_id` = ? and `user_id` in (' . appPlaceholders($adminIds) . ')' + . ' and `role` <> "owner" and `status` = 0' + ); + $update->execute(array_merge([$now, $groupId], $adminIds)); + } + + return ['adminIds' => $adminIds]; +} + +function updateGroupProfileAction(PDO $appDb, string $userId, string $groupId, array $payload): array +{ + requireWebGroupRole($appDb, $groupId, $userId, ['owner', 'admin']); + $name = trim((string)($payload['name'] ?? '')); + $announcement = trim((string)($payload['announcement'] ?? '')); + $welcomeText = trim((string)($payload['welcomeText'] ?? '')); + if ($name === '') { + respond(400, '请输入群名称'); + } + if (mb_strlen($name, 'UTF-8') > 128) { + respond(400, '群名称过长'); + } + + $now = webImNowMs(); + $statement = $appDb->prepare( + 'update `t_web_groups` set `name` = ?, `announcement` = ?, `welcome_text` = ?, `updated_at_ms` = ?' + . ' where `group_id` = ? and `deleted` = 0' + ); + $statement->execute([$name, $announcement, $welcomeText, $now, $groupId]); + + return [ + 'groupId' => $groupId, + 'name' => $name, + 'announcement' => $announcement, + 'welcomeText' => $welcomeText, + 'updatedAt' => $now, + ]; +} + +function kickGroupMembersAction(PDO $appDb, string $userId, string $groupId, array $targetIds): array +{ + $operator = requireWebGroupRole($appDb, $groupId, $userId, ['owner', 'admin']); + $operatorRole = (string)$operator['role']; + $targetIds = array_values(array_unique(array_filter(array_map('strval', $targetIds), static fn(string $uid): bool => $uid !== ''))); + $targetIds = array_values(array_filter($targetIds, static fn(string $uid): bool => $uid !== $userId)); + if (!$targetIds) { + respond(400, '请选择要移除的群员'); + } + + $statement = $appDb->prepare( + 'select `user_id`, `role` from `t_web_group_members`' + . ' where `group_id` = ? and `user_id` in (' . appPlaceholders($targetIds) . ') and `status` = 0' + ); + $statement->execute(array_merge([$groupId], $targetIds)); + $targets = $statement->fetchAll(); + if (!$targets) { + respond(400, '没有可移除的群员'); + } + + $allowedIds = []; + foreach ($targets as $target) { + $role = (string)$target['role']; + if ($role === 'owner') { + continue; + } + if ($operatorRole === 'admin' && $role !== 'member') { + continue; + } + $allowedIds[] = (string)$target['user_id']; + } + if (!$allowedIds) { + respond(403, '没有可移除的群员'); + } + + $now = webImNowMs(); + $update = $appDb->prepare( + 'update `t_web_group_members` set `status` = 2, `updated_at_ms` = ?' + . ' where `group_id` = ? and `user_id` in (' . appPlaceholders($allowedIds) . ')' + ); + $update->execute(array_merge([$now, $groupId], $allowedIds)); + + try { + removeNativeGroupMember($userId, $groupId, $allowedIds); + } catch (Throwable) { + } + + return ['removedIds' => $allowedIds]; +} + +function updateMuteSettingAction(PDO $appDb, string $userId, string $groupId, array $payload): array +{ + requireWebGroupRole($appDb, $groupId, $userId, ['owner', 'admin']); + $mode = trim((string)($payload['mode'] ?? 'off')); + $allowedModes = ['off', 'all_include_admins', 'all_except_admins']; + if (!in_array($mode, $allowedModes, true)) { + respond(400, '禁言模式不正确'); + } + $scheduleType = trim((string)($payload['scheduleType'] ?? 'once')); + if (!in_array($scheduleType, ['manual', 'once', 'daily'], true)) { + respond(400, '禁言时段类型不正确'); + } + + $startsAtMs = max(0, (int)($payload['startsAtMs'] ?? 0)); + $endsAtMs = max(0, (int)($payload['endsAtMs'] ?? 0)); + $dailyStart = trim((string)($payload['dailyStart'] ?? '')); + $dailyEnd = trim((string)($payload['dailyEnd'] ?? '')); + $enabled = $mode !== 'off'; + if ($enabled && $scheduleType === 'once' && ($startsAtMs <= 0 || $endsAtMs <= $startsAtMs)) { + respond(400, '请设置有效的单次禁言时段'); + } + if ($enabled && $scheduleType === 'daily' && (!preg_match('/^\d{2}:\d{2}$/', $dailyStart) || !preg_match('/^\d{2}:\d{2}$/', $dailyEnd))) { + respond(400, '请设置有效的每日禁言时段'); + } + + $now = webImNowMs(); + $statement = $appDb->prepare( + 'insert into `t_web_group_mute_rules`' + . ' (`group_id`, `mode`, `schedule_type`, `starts_at_ms`, `ends_at_ms`, `daily_start`, `daily_end`, `enabled`, `created_by`, `updated_at_ms`)' + . ' values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)' + . ' on duplicate key update' + . ' `mode` = values(`mode`),' + . ' `schedule_type` = values(`schedule_type`),' + . ' `starts_at_ms` = values(`starts_at_ms`),' + . ' `ends_at_ms` = values(`ends_at_ms`),' + . ' `daily_start` = values(`daily_start`),' + . ' `daily_end` = values(`daily_end`),' + . ' `enabled` = values(`enabled`),' + . ' `created_by` = values(`created_by`),' + . ' `updated_at_ms` = values(`updated_at_ms`)' + ); + $statement->execute([$groupId, $mode, $scheduleType, $startsAtMs, $endsAtMs, $dailyStart, $dailyEnd, $enabled ? 1 : 0, $userId, $now]); + + return ['muteSetting' => currentMuteSetting($appDb, $groupId)]; +} + +function joinWebGroupAction(PDO $appDb, string $userId, string $groupKey): array +{ + if ($groupKey === '') { + respond(400, '请输入群组ID或群名称'); + } + + $group = fetchWebGroup($appDb, $groupKey); + if (!$group) { + try { + $native = requireImSuccess(imAdminPost('/admin/group/get_info', ['groupId' => $groupKey]), '群组不存在'); + $now = webImNowMs(); + $statement = $appDb->prepare( + 'insert into `t_web_groups`' + . ' (`group_id`, `category`, `name`, `portrait`, `owner_uid`, `join_mode`, `announcement`,' + . ' `native_group_id`, `native_status`, `created_by`, `created_at_ms`, `updated_at_ms`, `deleted`)' + . ' values (?, "group", ?, ?, ?, "open", "", ?, "ok", ?, ?, ?, 0)' + . ' on duplicate key update `updated_at_ms` = values(`updated_at_ms`)' + ); + $statement->execute([ + $groupKey, + (string)($native['name'] ?? $groupKey), + (string)($native['portrait'] ?? ''), + (string)($native['owner'] ?? ''), + $groupKey, + $userId, + $now, + $now, + ]); + $group = fetchWebGroup($appDb, $groupKey); + } catch (Throwable) { + respond(404, '没有找到该群组'); + } + } + + $groupId = (string)$group['group_id']; + upsertWebGroupMember($appDb, $groupId, $userId, 'member'); + try { + addNativeGroupMember((string)($group['owner_uid'] ?: $userId), $groupId, $userId); + } catch (Throwable $error) { + // Web membership is authoritative for this web build; native group sync is best-effort. + } + + $role = fetchWebGroupRole($appDb, $groupId, $userId); + return [ + 'group' => fetchWebGroup($appDb, $groupId), + 'conversation' => webGroupConversation($group, $role), + ]; +} + +function sendBroadcastAction(PDO $imDb, PDO $appDb, string $userId, array $payload): array +{ + $text = trim((string)($payload['text'] ?? '')); + if ($text === '') { + respond(400, '请输入群发内容'); + } + + $targetIds = resolveWebUserIds($imDb, webActionList($payload['targetIds'] ?? [])); + $targetIds = array_values(array_filter(array_unique($targetIds), static fn(string $uid): bool => $uid !== $userId)); + if (!$targetIds) { + respond(400, '请选择群发对象'); + } + + $results = []; + foreach ($targetIds as $targetId) { + try { + $result = requireImSuccess(imAdminPost('/admin/message/send', [ + 'sender' => $userId, + 'conv' => [ + 'type' => 0, + 'target' => $targetId, + 'line' => 0, + ], + 'payload' => [ + 'type' => 1, + 'searchableContent' => $text, + 'pushContent' => $text, + 'content' => $text, + 'persistFlag' => 3, + ], + 'userMessage' => true, + ]), '发送失败'); + $results[] = [ + 'targetId' => $targetId, + 'ok' => true, + 'messageUid' => isset($result['messageUid']) ? (string)$result['messageUid'] : '', + ]; + } catch (Throwable $error) { + $results[] = [ + 'targetId' => $targetId, + 'ok' => false, + 'error' => $error->getMessage() ?: '发送失败', + ]; + } + } + + $successCount = count(array_filter($results, static fn(array $item): bool => !empty($item['ok']))); + $failedCount = count($results) - $successCount; + $now = webImNowMs(); + $statement = $appDb->prepare( + 'insert into `t_web_broadcast_logs`' + . ' (`sender_uid`, `target_count`, `success_count`, `failed_count`, `text`, `target_json`, `result_json`, `created_at_ms`)' + . ' values (?, ?, ?, ?, ?, ?, ?, ?)' + ); + $statement->execute([ + $userId, + count($targetIds), + $successCount, + $failedCount, + $text, + json_encode($targetIds, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), + json_encode($results, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), + $now, + ]); + + return [ + 'targetCount' => count($targetIds), + 'successCount' => $successCount, + 'failedCount' => $failedCount, + 'results' => $results, + ]; +} + +$payload = readJsonPayload(); +$action = trim((string)($payload['action'] ?? '')); +$userId = trim((string)($payload['userId'] ?? '')); + +if ($userId === '') { + respond(400, '缺少当前用户ID'); +} + +try { + ensureWebImAppTables(); + $imDb = imDb(); + $appDb = imAppDb(); + + if ($action === 'create_group') { + respond(0, 'success', createWebGroupAction($imDb, $appDb, $userId, $payload, 'group')); + } + if ($action === 'create_channel') { + respond(0, 'success', createWebGroupAction($imDb, $appDb, $userId, $payload, 'channel')); + } + if ($action === 'join_group') { + respond(0, 'success', joinWebGroupAction($appDb, $userId, trim((string)($payload['groupKey'] ?? '')))); + } + if ($action === 'group_detail') { + respond(0, 'success', groupDetailAction($imDb, $appDb, $userId, trim((string)($payload['groupId'] ?? '')))); + } + if ($action === 'group_members') { + respond(0, 'success', groupMembersAction($imDb, $appDb, $userId, trim((string)($payload['groupId'] ?? '')))); + } + if ($action === 'update_group_alias') { + respond(0, 'success', updateGroupAliasAction( + $appDb, + $userId, + trim((string)($payload['groupId'] ?? '')), + trim((string)($payload['alias'] ?? '')) + )); + } + if ($action === 'leave_group') { + respond(0, 'success', leaveGroupAction($appDb, $userId, trim((string)($payload['groupId'] ?? '')))); + } + if ($action === 'dismiss_group') { + respond(0, 'success', dismissGroupAction($appDb, $userId, trim((string)($payload['groupId'] ?? '')))); + } + if ($action === 'transfer_group') { + respond(0, 'success', transferGroupAction( + $appDb, + $userId, + trim((string)($payload['groupId'] ?? '')), + trim((string)($payload['targetUserId'] ?? '')) + )); + } + if ($action === 'set_group_admins') { + respond(0, 'success', setGroupAdminsAction( + $appDb, + $userId, + trim((string)($payload['groupId'] ?? '')), + is_array($payload['adminIds'] ?? null) ? $payload['adminIds'] : [] + )); + } + if ($action === 'update_group_profile') { + respond(0, 'success', updateGroupProfileAction( + $appDb, + $userId, + trim((string)($payload['groupId'] ?? '')), + $payload + )); + } + if ($action === 'kick_group_members') { + respond(0, 'success', kickGroupMembersAction( + $appDb, + $userId, + trim((string)($payload['groupId'] ?? '')), + is_array($payload['targetIds'] ?? null) ? $payload['targetIds'] : [] + )); + } + if ($action === 'update_mute_setting') { + respond(0, 'success', updateMuteSettingAction( + $appDb, + $userId, + trim((string)($payload['groupId'] ?? '')), + $payload + )); + } + if ($action === 'broadcast') { + $result = sendBroadcastAction($imDb, $appDb, $userId, $payload); + if ((int)$result['successCount'] <= 0) { + respond(500, '群发失败', $result); + } + respond(0, 'success', $result); + } + + respond(400, '未知操作'); +} catch (Throwable $error) { + respond(500, $error->getMessage() ?: '会话操作失败'); +} diff --git a/dist/api/im_web_bootstrap.php b/dist/api/im_web_bootstrap.php new file mode 100644 index 0000000..5a82201 --- /dev/null +++ b/dist/api/im_web_bootstrap.php @@ -0,0 +1,348 @@ + $code, + 'message' => $message, + 'result' => $result, + ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + exit; +} + +function readJsonPayload(): array +{ + $payload = json_decode(file_get_contents('php://input') ?: '[]', true); + return is_array($payload) ? $payload : []; +} + +function imAdminPost(string $path, array $payload): array +{ + $nonce = (string)random_int(1, 1000000); + $timestamp = (string)((int)floor(microtime(true) * 1000)); + $sign = sha1($nonce . '|' . IM_ADMIN_SECRET . '|' . $timestamp); + $body = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + + $curl = curl_init(IM_ADMIN_URL . $path); + if ($curl === false) { + throw new RuntimeException('IM admin client init failed'); + } + + curl_setopt_array($curl, [ + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => $body, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_0, + CURLOPT_CONNECTTIMEOUT_MS => 1000, + CURLOPT_TIMEOUT_MS => 4000, + CURLOPT_FRESH_CONNECT => true, + CURLOPT_FORBID_REUSE => true, + CURLOPT_HTTPHEADER => [ + 'Content-Type: application/json; charset=utf-8', + 'Connection: close', + 'nonce: ' . $nonce, + 'timestamp: ' . $timestamp, + 'sign: ' . $sign, + ], + ]); + + $responseBody = curl_exec($curl); + $curlError = curl_error($curl); + curl_close($curl); + + if ($responseBody === false || $responseBody === '') { + throw new RuntimeException($curlError ?: 'IM admin request failed'); + } + + $data = json_decode($responseBody, true); + if (!is_array($data)) { + throw new RuntimeException('Invalid IM admin response'); + } + + return $data; +} + +function requireImSuccess(array $response, string $fallbackMessage): array +{ + if (($response['code'] ?? -1) !== 0) { + throw new RuntimeException((string)($response['msg'] ?? $response['message'] ?? $fallbackMessage)); + } + + return is_array($response['result'] ?? null) ? $response['result'] : []; +} + +function imDb(): PDO +{ + static $pdo = null; + if ($pdo instanceof PDO) { + return $pdo; + } + + $pdo = new PDO(IM_DB_DSN, IM_DB_USER, IM_DB_PASSWORD, [ + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, + ]); + + return $pdo; +} + +function imAppDb(): PDO +{ + static $pdo = null; + if ($pdo instanceof PDO) { + return $pdo; + } + + $pdo = new PDO(IM_APP_DB_DSN, IM_APP_DB_USER, IM_APP_DB_PASSWORD, [ + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, + ]); + + return $pdo; +} + +function ensureWebImAppTables(): void +{ + static $ensured = false; + if ($ensured) { + return; + } + $ensured = true; + + $db = imAppDb(); + $db->exec( + "create table if not exists `t_web_groups` ( + `id` bigint unsigned not null auto_increment, + `group_id` varchar(64) not null, + `category` varchar(20) not null default 'group', + `name` varchar(128) not null, + `portrait` varchar(1024) not null default '', + `owner_uid` varchar(64) not null, + `join_mode` varchar(20) not null default 'open', + `announcement` text null, + `native_group_id` varchar(64) not null default '', + `native_status` varchar(32) not null default 'pending', + `native_error` varchar(255) not null default '', + `created_by` varchar(64) not null, + `created_at_ms` bigint not null, + `updated_at_ms` bigint not null, + `deleted` tinyint not null default 0, + primary key (`id`), + unique key `uk_t_web_groups_group_id` (`group_id`), + key `idx_t_web_groups_owner` (`owner_uid`), + key `idx_t_web_groups_category` (`category`) + ) engine=InnoDB default charset=utf8mb4 collate=utf8mb4_unicode_ci" + ); + try { + $db->exec("alter table `t_web_groups` add column `welcome_text` text null after `announcement`"); + } catch (Throwable) { + } + $db->exec( + "create table if not exists `t_web_group_members` ( + `id` bigint unsigned not null auto_increment, + `group_id` varchar(64) not null, + `user_id` varchar(64) not null, + `role` varchar(20) not null default 'member', + `alias` varchar(128) not null default '', + `joined_at_ms` bigint not null, + `updated_at_ms` bigint not null, + `status` tinyint not null default 0, + primary key (`id`), + unique key `uk_t_web_group_members_gid_uid` (`group_id`, `user_id`), + key `idx_t_web_group_members_user` (`user_id`) + ) engine=InnoDB default charset=utf8mb4 collate=utf8mb4_unicode_ci" + ); + $db->exec( + "create table if not exists `t_web_group_messages` ( + `id` bigint unsigned not null auto_increment, + `message_uid` varchar(64) not null, + `group_id` varchar(64) not null, + `sender_uid` varchar(64) not null, + `text` text not null, + `created_at_ms` bigint not null, + `edited_at_ms` bigint not null default 0, + `recalled` tinyint not null default 0, + primary key (`id`), + unique key `uk_t_web_group_messages_uid` (`message_uid`), + key `idx_t_web_group_messages_group_time` (`group_id`, `created_at_ms`), + key `idx_t_web_group_messages_sender` (`sender_uid`) + ) engine=InnoDB default charset=utf8mb4 collate=utf8mb4_unicode_ci" + ); + $db->exec( + "create table if not exists `t_web_message_attachments` ( + `id` bigint unsigned not null auto_increment, + `message_uid` varchar(64) not null, + `conversation_type` varchar(16) not null default 'single', + `target_id` varchar(128) not null default '', + `sender_uid` varchar(64) not null default '', + `attachment_type` varchar(16) not null, + `url` varchar(1024) not null, + `name` varchar(255) not null default '', + `mime` varchar(128) not null default '', + `size_bytes` bigint not null default 0, + `created_at_ms` bigint not null, + primary key (`id`), + unique key `uk_t_web_message_attachments_uid` (`message_uid`), + key `idx_t_web_message_attachments_target` (`conversation_type`, `target_id`, `created_at_ms`) + ) engine=InnoDB default charset=utf8mb4 collate=utf8mb4_unicode_ci" + ); + $db->exec( + "create table if not exists `t_web_group_reads` ( + `id` bigint unsigned not null auto_increment, + `group_id` varchar(64) not null, + `user_id` varchar(64) not null, + `read_at_ms` bigint not null default 0, + `updated_at_ms` bigint not null, + primary key (`id`), + unique key `uk_t_web_group_reads_gid_uid` (`group_id`, `user_id`) + ) engine=InnoDB default charset=utf8mb4 collate=utf8mb4_unicode_ci" + ); + $db->exec( + "create table if not exists `t_web_group_mute_rules` ( + `id` bigint unsigned not null auto_increment, + `group_id` varchar(64) not null, + `mode` varchar(32) not null default 'off', + `schedule_type` varchar(16) not null default 'once', + `starts_at_ms` bigint not null default 0, + `ends_at_ms` bigint not null default 0, + `daily_start` varchar(8) not null default '', + `daily_end` varchar(8) not null default '', + `enabled` tinyint not null default 0, + `created_by` varchar(64) not null default '', + `updated_at_ms` bigint not null, + primary key (`id`), + unique key `uk_t_web_group_mute_rules_group` (`group_id`) + ) engine=InnoDB default charset=utf8mb4 collate=utf8mb4_unicode_ci" + ); + $db->exec( + "create table if not exists `t_web_broadcast_logs` ( + `id` bigint unsigned not null auto_increment, + `sender_uid` varchar(64) not null, + `target_count` int not null default 0, + `success_count` int not null default 0, + `failed_count` int not null default 0, + `text` text not null, + `target_json` text null, + `result_json` text null, + `created_at_ms` bigint not null, + primary key (`id`), + key `idx_t_web_broadcast_logs_sender_time` (`sender_uid`, `created_at_ms`) + ) engine=InnoDB default charset=utf8mb4 collate=utf8mb4_unicode_ci" + ); + $db->exec( + "create table if not exists `t_web_user_remarks` ( + `id` bigint unsigned not null auto_increment, + `owner_uid` varchar(64) not null, + `target_uid` varchar(64) not null, + `alias` varchar(128) not null default '', + `created_at_ms` bigint not null, + `updated_at_ms` bigint not null, + primary key (`id`), + unique key `uk_t_web_user_remarks_owner_target` (`owner_uid`, `target_uid`), + key `idx_t_web_user_remarks_target` (`target_uid`) + ) engine=InnoDB default charset=utf8mb4 collate=utf8mb4_unicode_ci" + ); +} + +function webImPlaceholders(array $values): string +{ + return implode(',', array_fill(0, count($values), '?')); +} + +function fetchWebUserRemarkMap(PDO $db, string $ownerId, array $targetIds): array +{ + $targetIds = array_values(array_unique(array_filter(array_map(static function ($targetId): string { + return trim((string)$targetId); + }, $targetIds)))); + if ($ownerId === '' || !$targetIds) { + return []; + } + + $statement = $db->prepare( + 'select `target_uid`, `alias` from `t_web_user_remarks`' + . ' where `owner_uid` = ? and `target_uid` in (' . webImPlaceholders($targetIds) . ')' + ); + $statement->execute(array_merge([$ownerId], $targetIds)); + + $remarks = []; + foreach ($statement->fetchAll() as $row) { + $remarks[(string)$row['target_uid']] = trim((string)($row['alias'] ?? '')); + } + + return $remarks; +} + +function saveWebUserRemark(PDO $db, string $ownerId, string $targetId, string $alias): void +{ + if ($ownerId === '' || $targetId === '') { + return; + } + + if ($alias === '') { + $delete = $db->prepare('delete from `t_web_user_remarks` where `owner_uid` = ? and `target_uid` = ?'); + $delete->execute([$ownerId, $targetId]); + return; + } + + $now = webImNowMs(); + $statement = $db->prepare( + 'insert into `t_web_user_remarks` (`owner_uid`, `target_uid`, `alias`, `created_at_ms`, `updated_at_ms`)' + . ' values (?, ?, ?, ?, ?)' + . ' on duplicate key update `alias` = values(`alias`), `updated_at_ms` = values(`updated_at_ms`)' + ); + $statement->execute([$ownerId, $targetId, $alias, $now, $now]); +} + +function webImNowMs(): int +{ + return (int)floor(microtime(true) * 1000); +} + +function webImMessageUid(): string +{ + $timestampPart = (webImNowMs() - 1514736000000) << 21; + return (string)($timestampPart | random_int(1, 0x1FFFFF)); +} + +function javaStringHashCode(string $value): int +{ + $hash = 0; + $length = strlen($value); + for ($i = 0; $i < $length; $i++) { + $hash = (31 * $hash + ord($value[$i])) & 0xFFFFFFFF; + if ($hash >= 0x80000000) { + $hash -= 0x100000000; + } + } + + return $hash; +} + +function imUserMessagesTable(string $userId): string +{ + return 't_user_messages_' . (abs(javaStringHashCode($userId)) % 128); +} + +function imMessageTable(string $messageId): string +{ + $timestamp = ((int)$messageId >> 21) + 1514736000000; + $date = (new DateTimeImmutable('@' . (int)floor($timestamp / 1000))) + ->setTimezone(new DateTimeZone(IM_DB_TIMEZONE)); + $year = (int)$date->format('Y'); + $month = (int)$date->format('n') - 1; + + return 't_messages_' . (($year % 3) * 12 + $month); +} diff --git a/dist/api/media_download.php b/dist/api/media_download.php new file mode 100644 index 0000000..634c71d --- /dev/null +++ b/dist/api/media_download.php @@ -0,0 +1,58 @@ +prepare( + 'select `_uid` from `t_user` where `_uid` = ? or `_mobile` = ? or `_name` = ? limit 1' + ); + $statement->execute([$value, $value, $value]); + $uid = $statement->fetchColumn(); + + return is_string($uid) && $uid !== '' ? $uid : $value; +} + +function messageTimestampMs(string $messageUid): int +{ + return (((int)$messageUid) >> 21) + 1514736000000; +} + +function nowTimestampMs(): int +{ + return (int)floor(microtime(true) * 1000); +} + +function recallLimitMinutes(): float +{ + $statement = imAppDb()->prepare( + 'select `conf_value` from `system_conf`' + . ' where `conf_name` = ? and `is_valid` = 1 limit 1' + ); + $statement->execute(['RetractMessage']); + $value = trim((string)($statement->fetchColumn() ?: '0')); + + if ($value === '' || !is_numeric($value)) { + return 0.0; + } + + return max(0.0, (float)$value); +} + +function assertRecallWithinLimit(string $messageUid): void +{ + $limitMinutes = recallLimitMinutes(); + if ($limitMinutes <= 0) { + return; + } + + $messageAgeMs = nowTimestampMs() - messageTimestampMs($messageUid); + $limitMs = (int)round($limitMinutes * 60 * 1000); + if ($messageAgeMs > $limitMs) { + throw new UserActionException('已超过可撤回时间'); + } +} + +function fetchActionMessageRow(string $messageUid): array +{ + $table = imMessageTable($messageUid); + $statement = imDb()->prepare( + 'select `_mid`, `_from`, `_content_type`, `_searchable_key` from `' . $table . '` where `_mid` = ? limit 1' + ); + $statement->execute([$messageUid]); + $row = $statement->fetch(); + + return is_array($row) ? $row : []; +} + +function ensureMessageEditsTable(): void +{ + imDb()->exec( + 'create table if not exists `t_web_message_edits` (' + . '`message_uid` varchar(32) not null,' + . '`editor_uid` varchar(64) not null,' + . '`edited_text` text null,' + . '`edited_at_ms` bigint not null,' + . '`created_at` datetime not null default current_timestamp,' + . '`updated_at` datetime null default current_timestamp on update current_timestamp,' + . 'primary key (`message_uid`),' + . 'key `idx_editor_uid` (`editor_uid`),' + . 'key `idx_edited_at_ms` (`edited_at_ms`)' + . ') engine=InnoDB default charset=utf8mb4' + ); +} + +function editTextMessage(string $operatorId, string $messageUid, string $text): array +{ + $row = fetchActionMessageRow($messageUid); + if (!$row) { + return editWebGroupMessage($operatorId, $messageUid, $text); + } + if ((string)$row['_from'] !== $operatorId) { + throw new UserActionException('只能编辑自己发送的消息'); + } + if ((int)$row['_content_type'] !== 1) { + throw new UserActionException('只能编辑文字消息'); + } + + $editedAt = nowTimestampMs(); + $table = imMessageTable($messageUid); + $update = imDb()->prepare('update `' . $table . '` set `_searchable_key` = ? where `_mid` = ?'); + $update->execute([$text, $messageUid]); + + ensureMessageEditsTable(); + $upsert = imDb()->prepare( + 'insert into `t_web_message_edits` (`message_uid`, `editor_uid`, `edited_text`, `edited_at_ms`)' + . ' values (?, ?, ?, ?)' + . ' on duplicate key update' + . ' `editor_uid` = values(`editor_uid`),' + . ' `edited_text` = values(`edited_text`),' + . ' `edited_at_ms` = values(`edited_at_ms`),' + . ' `updated_at` = current_timestamp' + ); + $upsert->execute([$messageUid, $operatorId, $text, $editedAt]); + + return [ + 'messageUid' => $messageUid, + 'text' => $text, + 'editedAt' => $editedAt, + ]; +} + +function fetchWebGroupActionMessage(string $messageUid): array +{ + ensureWebImAppTables(); + $statement = imAppDb()->prepare( + 'select m.*, g.`category` from `t_web_group_messages` m' + . ' join `t_web_groups` g on g.`group_id` = m.`group_id` and g.`deleted` = 0' + . ' where m.`message_uid` = ? limit 1' + ); + $statement->execute([$messageUid]); + $row = $statement->fetch(); + + return is_array($row) ? $row : []; +} + +function editWebGroupMessage(string $operatorId, string $messageUid, string $text): array +{ + $row = fetchWebGroupActionMessage($messageUid); + if (!$row) { + throw new UserActionException('消息不存在'); + } + if ((string)$row['sender_uid'] !== $operatorId) { + throw new UserActionException('只能编辑自己发送的消息'); + } + if ((int)($row['recalled'] ?? 0) !== 0) { + throw new UserActionException('已撤回的消息请重新编辑发送'); + } + + $editedAt = nowTimestampMs(); + $update = imAppDb()->prepare( + 'update `t_web_group_messages` set `text` = ?, `edited_at_ms` = ? where `message_uid` = ?' + ); + $update->execute([$text, $editedAt, $messageUid]); + $groupUpdate = imAppDb()->prepare('update `t_web_groups` set `updated_at_ms` = ? where `group_id` = ?'); + $groupUpdate->execute([$editedAt, (string)$row['group_id']]); + + return [ + 'messageUid' => $messageUid, + 'text' => $text, + 'editedAt' => $editedAt, + ]; +} + +function webGroupMemberRoleForAction(string $groupId, string $userId): string +{ + $statement = imAppDb()->prepare( + 'select `role` from `t_web_group_members`' + . ' where `group_id` = ? and `user_id` = ? and `status` = 0 limit 1' + ); + $statement->execute([$groupId, $userId]); + $role = $statement->fetchColumn(); + + return is_string($role) ? $role : ''; +} + +function recallWebGroupMessage(string $operatorId, string $messageUid): bool +{ + $row = fetchWebGroupActionMessage($messageUid); + if (!$row) { + return false; + } + $isSender = (string)$row['sender_uid'] === $operatorId; + if (!$isSender) { + $role = webGroupMemberRoleForAction((string)$row['group_id'], $operatorId); + if (!in_array($role, ['owner', 'admin'], true)) { + throw new UserActionException('只有群主或管理员可以撤回他人消息'); + } + } + assertRecallWithinLimit($messageUid); + + $now = nowTimestampMs(); + $update = imAppDb()->prepare( + 'update `t_web_group_messages` set `recalled` = 1 where `message_uid` = ?' + ); + $update->execute([$messageUid]); + $groupUpdate = imAppDb()->prepare('update `t_web_groups` set `updated_at_ms` = ? where `group_id` = ?'); + $groupUpdate->execute([$now, (string)$row['group_id']]); + + return true; +} + +$payload = readJsonPayload(); +$action = trim((string)($payload['action'] ?? '')); +$userId = trim((string)($payload['userId'] ?? '')); +$messageUidText = trim((string)($payload['messageUid'] ?? '')); +$messageUid = ctype_digit($messageUidText) ? (int)$messageUidText : 0; + +if ($action === '' || $messageUid <= 0) { + respond(400, '缺少操作类型或消息ID'); +} + +try { + if ($action === 'recall') { + if ($userId === '') { + respond(400, '缺少操作者ID'); + } + $userId = resolveActionUserId($userId); + $row = fetchActionMessageRow($messageUidText); + if (!$row && recallWebGroupMessage($userId, $messageUidText)) { + respond(0, 'success', [ + 'messageUid' => (string)$messageUid, + 'action' => $action, + ]); + } + if (!$row) { + throw new UserActionException('消息不存在'); + } + assertRecallWithinLimit($messageUidText); + requireImSuccess(imAdminPost('/admin/message/recall', [ + 'operator' => $userId, + 'messageUid' => $messageUid, + 'userRecall' => true, + 'isUserRecall' => true, + ]), '撤回失败'); + + respond(0, 'success', [ + 'messageUid' => (string)$messageUid, + 'action' => $action, + ]); + } + + if ($action === 'edit') { + if ($userId === '') { + respond(400, '缺少操作者ID'); + } + $text = trim((string)($payload['text'] ?? '')); + if ($text === '') { + respond(400, '编辑内容不能为空'); + } + if (mb_strlen($text, 'UTF-8') > 5000) { + respond(400, '编辑内容过长'); + } + + $userId = resolveActionUserId($userId); + respond(0, 'success', [ + 'message' => editTextMessage($userId, $messageUidText, $text), + 'action' => $action, + ]); + } + + if ($action === 'delete') { + requireImSuccess(imAdminPost('/admin/message/delete', [ + 'messageUid' => $messageUid, + ]), '删除失败'); + + respond(0, 'success', [ + 'messageUid' => (string)$messageUid, + 'action' => $action, + ]); + } + + respond(400, '不支持的消息操作'); +} catch (Throwable $error) { + respond($error instanceof UserActionException ? 400 : 500, $error->getMessage() ?: '消息操作失败'); +} diff --git a/dist/api/message_detail.php b/dist/api/message_detail.php new file mode 100644 index 0000000..06454bf --- /dev/null +++ b/dist/api/message_detail.php @@ -0,0 +1,387 @@ + 0) { + return ($id >> 21) + 1514736000000; + } + + if ($datetime !== null && $datetime !== '') { + $date = DateTimeImmutable::createFromFormat( + 'Y-m-d H:i:s', + $datetime, + new DateTimeZone(IM_DB_TIMEZONE) + ); + if ($date instanceof DateTimeImmutable) { + return $date->getTimestamp() * 1000; + } + } + + return 0; +} + +function detailResolveUserId(PDO $db, string $value): string +{ + if ($value === '') { + return ''; + } + + $statement = $db->prepare( + 'select `_uid` from `t_user` where `_uid` = ? or `_mobile` = ? or `_name` = ? limit 1' + ); + $statement->execute([$value, $value, $value]); + $uid = $statement->fetchColumn(); + + return is_string($uid) && $uid !== '' ? $uid : $value; +} + +function detailConversationType(mixed $value): string +{ + $type = strtolower(trim((string)$value)); + return in_array($type, ['1', 'group', 'channel'], true) ? 'group' : 'single'; +} + +function detailDeliveryMap(PDO $db, array $userIds): array +{ + $userIds = array_values(array_unique(array_filter(array_map('strval', $userIds)))); + if (!$userIds) { + return []; + } + + $statement = $db->prepare( + 'select `_uid`, max(`_dt`) as `_dt` from `t_delivery_report`' + . ' where `_uid` in (' . detailPlaceholders($userIds) . ') group by `_uid`' + ); + $statement->execute($userIds); + + $items = []; + foreach ($statement->fetchAll() as $row) { + $items[(string)$row['_uid']] = (int)$row['_dt']; + } + + return $items; +} + +function detailReadAt(PDO $db, string $readerId, array $targets): int +{ + $targets = array_values(array_unique(array_filter(array_map('strval', $targets)))); + if ($readerId === '' || !$targets) { + return 0; + } + + $statement = $db->prepare( + 'select max(`_dt`) from `t_read_report`' + . ' where `_uid` = ? and `_type` = 0 and `_line` = 0' + . ' and `_target` in (' . detailPlaceholders($targets) . ')' + ); + $statement->execute(array_merge([$readerId], $targets)); + $value = $statement->fetchColumn(); + + return is_numeric($value) ? (int)$value : 0; +} + +function detailSingleMessageVisible(PDO $db, string $userId, string $targetId, string $messageUid): bool +{ + $table = imUserMessagesTable($userId); + $conversationKeys = [$userId . '|' . $targetId, $targetId . '|' . $userId]; + $statement = $db->prepare( + 'select 1 from `' . $table . '`' + . ' where `_uid` = ? and `_mid` = ? and `_type` = 0 and `_line` = 0' + . ' and `_target` in (' . detailPlaceholders($conversationKeys) . ') limit 1' + ); + $statement->execute(array_merge([$userId, $messageUid], $conversationKeys)); + + return (bool)$statement->fetchColumn(); +} + +function detailUserMap(PDO $imDb, PDO $appDb, array $userIds, string $ownerId, string $groupId = ''): array +{ + $userIds = array_values(array_unique(array_filter(array_map('strval', $userIds)))); + if (!$userIds) { + return []; + } + + $statement = $imDb->prepare( + 'select `_uid`, `_name`, `_display_name`, `_mobile`, `_portrait` from `t_user`' + . ' where `_uid` in (' . detailPlaceholders($userIds) . ')' + ); + $statement->execute($userIds); + + $users = []; + foreach ($statement->fetchAll() as $row) { + $uid = (string)$row['_uid']; + $rawName = trim((string)($row['_display_name'] ?? '')) + ?: trim((string)($row['_name'] ?? '')) + ?: trim((string)($row['_mobile'] ?? '')) + ?: $uid; + $users[$uid] = [ + 'uid' => $uid, + 'rawName' => $rawName, + 'mobile' => (string)($row['_mobile'] ?? ''), + 'portrait' => (string)($row['_portrait'] ?? ''), + 'contactAlias' => '', + 'remarkAlias' => '', + 'groupAlias' => '', + 'role' => '', + 'displayName' => $rawName, + ]; + } + + if ($ownerId !== '') { + $aliasStatement = $imDb->prepare( + 'select `_friend_uid`, `_alias` from `t_friend` where `_uid` = ?' + . ' and `_friend_uid` in (' . detailPlaceholders($userIds) . ')' + ); + $aliasStatement->execute(array_merge([$ownerId], $userIds)); + foreach ($aliasStatement->fetchAll() as $row) { + $uid = (string)$row['_friend_uid']; + if (isset($users[$uid])) { + $users[$uid]['contactAlias'] = trim((string)($row['_alias'] ?? '')); + } + } + + try { + $remarkMap = fetchWebUserRemarkMap($appDb, $ownerId, $userIds); + } catch (Throwable) { + $remarkMap = []; + } + foreach ($remarkMap as $uid => $alias) { + if (isset($users[$uid])) { + $users[$uid]['remarkAlias'] = trim((string)$alias); + } + } + } + + if ($groupId !== '') { + $memberStatement = $appDb->prepare( + 'select `user_id`, `alias`, `role` from `t_web_group_members`' + . ' where `group_id` = ? and `user_id` in (' . detailPlaceholders($userIds) . ')' + ); + $memberStatement->execute(array_merge([$groupId], $userIds)); + foreach ($memberStatement->fetchAll() as $row) { + $uid = (string)$row['user_id']; + if (isset($users[$uid])) { + $users[$uid]['groupAlias'] = trim((string)($row['alias'] ?? '')); + $users[$uid]['role'] = (string)($row['role'] ?? ''); + } + } + } + + foreach ($users as $uid => $user) { + $alias = (string)$user['contactAlias'] !== '' + ? (string)$user['contactAlias'] + : ((string)$user['remarkAlias'] !== '' ? (string)$user['remarkAlias'] : (string)$user['groupAlias']); + $users[$uid]['displayName'] = $alias !== '' ? $alias : (string)$user['rawName']; + } + + foreach ($userIds as $uid) { + if (!isset($users[$uid])) { + $users[$uid] = [ + 'uid' => $uid, + 'rawName' => $uid, + 'mobile' => '', + 'portrait' => '', + 'contactAlias' => '', + 'remarkAlias' => '', + 'groupAlias' => '', + 'role' => '', + 'displayName' => $uid, + ]; + } + } + + return $users; +} + +function detailMember(array $user, int $time = 0): array +{ + return [ + 'uid' => (string)$user['uid'], + 'name' => (string)$user['displayName'], + 'rawName' => (string)$user['rawName'], + 'mobile' => (string)($user['mobile'] ?? ''), + 'portrait' => (string)($user['portrait'] ?? ''), + 'role' => (string)($user['role'] ?? ''), + 'time' => $time, + ]; +} + +function singleDetail(PDO $imDb, string $userId, string $targetId, string $messageUid): array +{ + if (!preg_match('/^\d+$/', $messageUid)) { + respond(400, '缺少可查询的消息ID'); + } + + $table = imMessageTable($messageUid); + $statement = $imDb->prepare( + 'select `_mid`, `_from`, `_target`, `_dt`, `_content_type`, `_searchable_key`, `_to`' + . ' from `' . $table . '` where `_mid` = ? limit 1' + ); + $statement->execute([$messageUid]); + $message = $statement->fetch(); + if (!is_array($message)) { + respond(404, '没有找到该消息'); + } + + $senderId = (string)$message['_from']; + if (!detailSingleMessageVisible($imDb, $userId, $targetId, $messageUid)) { + respond(403, '没有权限查看该消息'); + } + + $receiverId = $senderId === $userId ? $targetId : $userId; + if ($senderId !== $userId && $senderId !== $targetId) { + respond(403, '没有权限查看该消息'); + } + + $sentAt = detailTimestampMs((string)($message['_dt'] ?? ''), $messageUid); + $targets = [$senderId, $senderId . '|' . $receiverId, $receiverId . '|' . $senderId]; + $deliveredRaw = detailDeliveryMap($imDb, [$receiverId])[$receiverId] ?? 0; + $readRaw = detailReadAt($imDb, $receiverId, $targets); + $users = detailUserMap($imDb, imAppDb(), [$senderId, $receiverId], $userId); + + return [ + 'messageUid' => $messageUid, + 'conversationType' => 'single', + 'sentAt' => $sentAt, + 'sender' => detailMember($users[$senderId]), + 'receiver' => detailMember($users[$receiverId]), + 'deliveredAt' => $deliveredRaw >= $sentAt ? $deliveredRaw : 0, + 'readAt' => $readRaw >= $sentAt ? $readRaw : 0, + ]; +} + +function groupDetail(PDO $imDb, PDO $appDb, string $userId, string $groupId, string $messageUid): array +{ + ensureWebImAppTables(); + if (!preg_match('/^\d+$/', $messageUid)) { + respond(400, '缺少可查询的消息ID'); + } + + $myMember = $appDb->prepare( + 'select `role` from `t_web_group_members` where `group_id` = ? and `user_id` = ? and `status` = 0 limit 1' + ); + $myMember->execute([$groupId, $userId]); + if (!$myMember->fetchColumn()) { + respond(403, '你不在该群组中'); + } + + $messageStatement = $appDb->prepare( + 'select `message_uid`, `group_id`, `sender_uid`, `created_at_ms`, `edited_at_ms`, `recalled`' + . ' from `t_web_group_messages` where `group_id` = ? and `message_uid` = ? limit 1' + ); + $messageStatement->execute([$groupId, $messageUid]); + $message = $messageStatement->fetch(); + if (!is_array($message)) { + respond(404, '没有找到该群消息'); + } + + $sentAt = (int)$message['created_at_ms']; + $editedAt = (int)($message['edited_at_ms'] ?? 0); + $activityAt = max($sentAt, $editedAt); + $senderId = (string)$message['sender_uid']; + + $memberStatement = $appDb->prepare( + 'select `user_id`, `role`, `alias`, `joined_at_ms` from `t_web_group_members`' + . ' where `group_id` = ? and `status` = 0 and `joined_at_ms` <= ?' + . ' order by (`role` = "owner") desc, (`role` = "admin") desc, `joined_at_ms` asc' + ); + $memberStatement->execute([$groupId, $sentAt]); + $members = array_values(array_filter($memberStatement->fetchAll(), static function (array $member) use ($senderId): bool { + return (string)$member['user_id'] !== $senderId; + })); + + $memberIds = array_values(array_unique(array_map(static fn(array $member): string => (string)$member['user_id'], $members))); + $users = detailUserMap($imDb, $appDb, array_merge([$senderId], $memberIds), $userId, $groupId); + $deliveryMap = detailDeliveryMap($imDb, $memberIds); + + $readMap = []; + if ($memberIds) { + $readStatement = $appDb->prepare( + 'select `user_id`, `read_at_ms` from `t_web_group_reads`' + . ' where `group_id` = ? and `user_id` in (' . detailPlaceholders($memberIds) . ')' + ); + $readStatement->execute(array_merge([$groupId], $memberIds)); + foreach ($readStatement->fetchAll() as $row) { + $readMap[(string)$row['user_id']] = (int)$row['read_at_ms']; + } + } + + $read = []; + $unread = []; + $undelivered = []; + foreach ($memberIds as $uid) { + $readAt = (int)($readMap[$uid] ?? 0); + $deliveredAt = (int)($deliveryMap[$uid] ?? 0); + $member = detailMember($users[$uid], $readAt >= $activityAt ? $readAt : ($deliveredAt >= $sentAt ? $deliveredAt : 0)); + if ($readAt >= $activityAt) { + $read[] = $member; + } elseif ($deliveredAt >= $sentAt) { + $unread[] = $member; + } else { + $undelivered[] = $member; + } + } + + return [ + 'messageUid' => $messageUid, + 'conversationType' => 'group', + 'groupId' => $groupId, + 'sentAt' => $sentAt, + 'editedAt' => $editedAt, + 'recalled' => (bool)$message['recalled'], + 'sender' => detailMember($users[$senderId] ?? ['uid' => $senderId, 'displayName' => $senderId, 'rawName' => $senderId]), + 'group' => [ + 'read' => $read, + 'unread' => $unread, + 'undelivered' => $undelivered, + 'counts' => [ + 'read' => count($read), + 'unread' => count($unread), + 'undelivered' => count($undelivered), + 'total' => count($memberIds), + ], + ], + ]; +} + +$payload = readJsonPayload(); +$userId = trim((string)($payload['userId'] ?? '')); +$target = trim((string)($payload['target'] ?? '')); +$messageUid = trim((string)($payload['messageUid'] ?? $payload['messageId'] ?? '')); +$conversationType = detailConversationType($payload['conversationType'] ?? $payload['type'] ?? 'single'); + +if ($userId === '') { + respond(400, '缺少用户ID'); +} +if ($target === '') { + respond(400, '缺少会话对象'); +} +if ($messageUid === '') { + respond(400, '缺少消息ID'); +} + +try { + $imDb = imDb(); + $appDb = imAppDb(); + $userId = detailResolveUserId($imDb, $userId); + if ($conversationType === 'group') { + respond(0, 'success', ['detail' => groupDetail($imDb, $appDb, $userId, $target, $messageUid)]); + } + + $targetId = detailResolveUserId($imDb, $target); + respond(0, 'success', ['detail' => singleDetail($imDb, $userId, $targetId, $messageUid)]); +} catch (Throwable $error) { + respond(500, $error->getMessage() ?: '消息详情加载失败'); +} diff --git a/dist/api/messages.php b/dist/api/messages.php new file mode 100644 index 0000000..ac03851 --- /dev/null +++ b/dist/api/messages.php @@ -0,0 +1,900 @@ + 0) { + return ($id >> 21) + 1514736000000; + } + + if ($datetime !== null && $datetime !== '') { + $date = DateTimeImmutable::createFromFormat( + 'Y-m-d H:i:s', + $datetime, + new DateTimeZone(IM_DB_TIMEZONE) + ); + if ($date instanceof DateTimeImmutable) { + return $date->getTimestamp() * 1000; + } + } + + return (int)floor(microtime(true) * 1000); +} + +function nowMs(): int +{ + return (int)floor(microtime(true) * 1000); +} + +function markUserDelivered(PDO $db, string $userId, int $timestamp): void +{ + if ($userId === '' || $timestamp <= 0) { + return; + } + + $statement = $db->prepare( + 'insert into `t_delivery_report` (`_uid`, `_dt`) values (?, ?)' + . ' on duplicate key update `_dt` = if(`_dt` < values(`_dt`), values(`_dt`), `_dt`)' + ); + $statement->execute([$userId, $timestamp]); +} + +function markConversationRead(PDO $db, string $userId, string $targetId, int $timestamp): void +{ + if ($userId === '' || $targetId === '' || $timestamp <= 0) { + return; + } + + $statement = $db->prepare( + 'select `id`, `_dt` from `t_read_report`' + . ' where `_uid` = ? and `_type` = 0 and `_line` = 0 and `_target` = ?' + . ' order by `_dt` desc limit 1' + ); + $statement->execute([$userId, $targetId]); + $row = $statement->fetch(); + + if ($row) { + if ($timestamp > (int)$row['_dt']) { + $update = $db->prepare('update `t_read_report` set `_dt` = ? where `id` = ?'); + $update->execute([$timestamp, (int)$row['id']]); + } + return; + } + + $insert = $db->prepare( + 'insert into `t_read_report` (`_uid`, `_type`, `_line`, `_target`, `_dt`) values (?, 0, 0, ?, ?)' + ); + $insert->execute([$userId, $targetId, $timestamp]); +} + +function resolveUserId(PDO $db, string $value): string +{ + if ($value === '') { + return ''; + } + + $statement = $db->prepare( + 'select `_uid` from `t_user` where `_uid` = ? or `_mobile` = ? or `_name` = ? limit 1' + ); + $statement->execute([$value, $value, $value]); + $uid = $statement->fetchColumn(); + + return is_string($uid) && $uid !== '' ? $uid : $value; +} + +function peerFromConversationTarget(string $userId, string $target): string +{ + $parts = array_values(array_filter(explode('|', $target), static fn($part) => $part !== '')); + if (count($parts) < 2) { + return $target !== $userId ? $target : ''; + } + + return $parts[0] === $userId ? $parts[1] : $parts[0]; +} + +function fetchMessageRows(PDO $db, array $messageIds): array +{ + $messageIds = array_values(array_unique(array_filter(array_map('strval', $messageIds)))); + if (!$messageIds) { + return []; + } + + $idsByTable = []; + foreach ($messageIds as $messageId) { + $idsByTable[imMessageTable($messageId)][] = $messageId; + } + + $messages = []; + foreach ($idsByTable as $table => $ids) { + $sql = 'select `_mid`, `_from`, `_type`, `_target`, `_line`, `_searchable_key`, `_dt`, `_content_type`, `_to`' + . ' from `' . $table . '` where `_mid` in (' . placeholders($ids) . ')'; + $statement = $db->prepare($sql); + $statement->execute($ids); + foreach ($statement->fetchAll() as $row) { + $messages[(string)$row['_mid']] = $row; + } + } + + return $messages; +} + +function fetchMessageEditMap(PDO $db, array $messageIds): array +{ + $messageIds = array_values(array_unique(array_filter(array_map('strval', $messageIds)))); + if (!$messageIds) { + return []; + } + + try { + $statement = $db->prepare( + 'select `message_uid`, `edited_at_ms` from `t_web_message_edits`' + . ' where `message_uid` in (' . placeholders($messageIds) . ')' + ); + $statement->execute($messageIds); + } catch (Throwable) { + return []; + } + + $edits = []; + foreach ($statement->fetchAll() as $row) { + $edits[(string)$row['message_uid']] = [ + 'editedAt' => (int)$row['edited_at_ms'], + ]; + } + + return $edits; +} + +function normalizeAttachmentRow(array $row): array +{ + return [ + 'type' => (string)$row['attachment_type'], + 'url' => (string)$row['url'], + 'name' => (string)($row['name'] ?? ''), + 'mime' => (string)($row['mime'] ?? ''), + 'size' => (int)($row['size_bytes'] ?? 0), + ]; +} + +function fetchAttachmentMap(PDO $appDb, array $messageIds): array +{ + $messageIds = array_values(array_unique(array_filter(array_map('strval', $messageIds)))); + if (!$messageIds) { + return []; + } + + ensureWebImAppTables(); + $statement = $appDb->prepare( + 'select `message_uid`, `attachment_type`, `url`, `name`, `mime`, `size_bytes`' + . ' from `t_web_message_attachments`' + . ' where `message_uid` in (' . placeholders($messageIds) . ')' + ); + $statement->execute($messageIds); + + $attachments = []; + foreach ($statement->fetchAll() as $row) { + $attachments[(string)$row['message_uid']] = normalizeAttachmentRow($row); + } + + return $attachments; +} + +function attachmentFallbackText(?array $attachment): string +{ + if (!$attachment) { + return ''; + } + if ((string)$attachment['type'] === 'image') { + return '[图片]'; + } + + return '[文件] ' . ((string)($attachment['name'] ?? '') ?: '文件'); +} + +function attachmentDisplayText(string $text, ?array $attachment): string +{ + if (!$attachment) { + return $text; + } + + return $text === attachmentFallbackText($attachment) ? '' : $text; +} + +function messagePreviewText(?array $message): string +{ + if (!$message) { + return ''; + } + $attachment = is_array($message['attachment'] ?? null) ? $message['attachment'] : null; + if ($attachment) { + $text = trim((string)($message['text'] ?? '')); + return $text !== '' ? $text : attachmentFallbackText($attachment); + } + + return (string)($message['text'] ?? ''); +} + +function fetchUserInfoMap(PDO $db, array $userIds, string $ownerId = ''): array +{ + $userIds = array_values(array_unique(array_filter(array_map('strval', $userIds)))); + if (!$userIds) { + return []; + } + + $statement = $db->prepare( + 'select `_uid`, `_name`, `_display_name`, `_mobile`, `_portrait`, `_address` from `t_user`' + . ' where `_uid` in (' . placeholders($userIds) . ')' + ); + $statement->execute($userIds); + + $users = []; + foreach ($statement->fetchAll() as $row) { + $row['_is_friend'] = false; + $row['_alias'] = ''; + $row['_friend_alias'] = ''; + $row['_remark_alias'] = ''; + $users[(string)$row['_uid']] = $row; + } + + if ($ownerId !== '') { + $aliasStatement = $db->prepare( + 'select `_friend_uid`, `_alias` from `t_friend` where `_uid` = ?' + . ' and `_friend_uid` in (' . placeholders($userIds) . ')' + ); + $aliasStatement->execute(array_merge([$ownerId], $userIds)); + foreach ($aliasStatement->fetchAll() as $row) { + $friendUid = (string)$row['_friend_uid']; + if (!isset($users[$friendUid])) { + continue; + } + $users[$friendUid]['_is_friend'] = true; + $users[$friendUid]['_friend_alias'] = trim((string)($row['_alias'] ?? '')); + } + + try { + ensureWebImAppTables(); + $remarkMap = fetchWebUserRemarkMap(imAppDb(), $ownerId, $userIds); + } catch (Throwable) { + $remarkMap = []; + } + foreach ($remarkMap as $targetUid => $alias) { + if (!isset($users[$targetUid])) { + continue; + } + $users[$targetUid]['_remark_alias'] = $alias; + } + + foreach ($users as $uid => $user) { + $friendAlias = trim((string)($user['_friend_alias'] ?? '')); + $remarkAlias = trim((string)($user['_remark_alias'] ?? '')); + $users[$uid]['_alias'] = $friendAlias !== '' ? $friendAlias : $remarkAlias; + } + } + + return $users; +} + +function userTitle(array $users, string $userId): string +{ + $user = $users[$userId] ?? null; + if (!$user) { + return $userId; + } + + return (string)($user['_alias'] ?? '') + ?: (string)($user['_display_name'] ?? '') + ?: (string)($user['_name'] ?? '') + ?: (string)($user['_mobile'] ?? '') + ?: $userId; +} + +function loadPeerReceiptState(PDO $db, string $currentUserId, string $peerId): array +{ + $deliveryStatement = $db->prepare('select `_dt` from `t_delivery_report` where `_uid` = ? limit 1'); + $deliveryStatement->execute([$peerId]); + $deliveredAt = $deliveryStatement->fetchColumn(); + + $readTargets = array_values(array_unique([ + $currentUserId, + $currentUserId . '|' . $peerId, + $peerId . '|' . $currentUserId, + ])); + $readStatement = $db->prepare( + 'select max(`_dt`) from `t_read_report`' + . ' where `_uid` = ? and `_type` = 0 and `_line` = 0' + . ' and `_target` in (' . placeholders($readTargets) . ')' + ); + $readStatement->execute(array_merge([$peerId], $readTargets)); + $readAt = $readStatement->fetchColumn(); + + return [ + 'deliveredAt' => is_numeric($deliveredAt) ? (int)$deliveredAt : 0, + 'readAt' => is_numeric($readAt) ? (int)$readAt : 0, + ]; +} + +function loadOwnReadStateMap(PDO $db, string $userId, array $peerIds): array +{ + $peerIds = array_values(array_unique(array_filter(array_map('strval', $peerIds)))); + if (!$peerIds) { + return []; + } + + $statement = $db->prepare( + 'select `_target`, max(`_dt`) as `_read_at` from `t_read_report`' + . ' where `_uid` = ? and `_type` = 0 and `_line` = 0' + . ' and `_target` in (' . placeholders($peerIds) . ')' + . ' group by `_target`' + ); + $statement->execute(array_merge([$userId], $peerIds)); + + $readAtByPeer = []; + foreach ($statement->fetchAll() as $row) { + $readAtByPeer[(string)$row['_target']] = (int)$row['_read_at']; + } + + return $readAtByPeer; +} + +function outgoingDeliveryStatus(int $timestamp, array $receiptState): string +{ + $readAt = (int)($receiptState['readAt'] ?? 0); + if ($readAt > 0 && $timestamp <= $readAt) { + return 'read'; + } + + $deliveredAt = (int)($receiptState['deliveredAt'] ?? 0); + if ($deliveredAt > 0 && $timestamp <= $deliveredAt) { + return 'delivered'; + } + + return 'sent'; +} + +function messageActivityTimestamp(int $timestamp, array $editState = []): int +{ + return max($timestamp, (int)($editState['editedAt'] ?? 0)); +} + +function normalizeConversationType(mixed $value): string +{ + $type = strtolower(trim((string)$value)); + if (in_array($type, ['1', 'group', 'channel'], true)) { + return 'group'; + } + + return 'single'; +} + +function isWebGroupTarget(PDO $appDb, string $targetId): bool +{ + if ($targetId === '') { + return false; + } + + ensureWebImAppTables(); + $statement = $appDb->prepare('select 1 from `t_web_groups` where `group_id` = ? and `deleted` = 0 limit 1'); + $statement->execute([$targetId]); + + return (bool)$statement->fetchColumn(); +} + +function markWebGroupRead(PDO $appDb, string $userId, string $groupId, int $timestamp): void +{ + if ($userId === '' || $groupId === '' || $timestamp <= 0) { + return; + } + + $now = nowMs(); + $statement = $appDb->prepare( + 'insert into `t_web_group_reads` (`group_id`, `user_id`, `read_at_ms`, `updated_at_ms`)' + . ' values (?, ?, ?, ?)' + . ' on duplicate key update' + . ' `read_at_ms` = if(`read_at_ms` < values(`read_at_ms`), values(`read_at_ms`), `read_at_ms`),' + . ' `updated_at_ms` = values(`updated_at_ms`)' + ); + $statement->execute([$groupId, $userId, $timestamp, $now]); +} + +function webGroupRole(PDO $appDb, string $userId, string $groupId): string +{ + $statement = $appDb->prepare( + 'select `role` from `t_web_group_members`' + . ' where `group_id` = ? and `user_id` = ? and `status` = 0 limit 1' + ); + $statement->execute([$groupId, $userId]); + $role = $statement->fetchColumn(); + + return is_string($role) && $role !== '' ? $role : ''; +} + +function formatWebGroupMessage( + array $row, + string $currentUserId, + array $group, + ?array $attachment = null, + array $users = [] +): array +{ + $groupId = (string)$row['group_id']; + $from = (string)$row['sender_uid']; + $senderUser = $users[$from] ?? []; + $senderAlias = trim((string)($row['sender_alias'] ?? '')); + $senderContactAlias = trim((string)($senderUser['_alias'] ?? '')); + $senderFriendAlias = trim((string)($senderUser['_friend_alias'] ?? '')); + $senderRemarkAlias = trim((string)($senderUser['_remark_alias'] ?? '')); + $senderRawName = trim((string)($senderUser['_display_name'] ?? '')) + ?: trim((string)($senderUser['_name'] ?? '')) + ?: trim((string)($senderUser['_mobile'] ?? '')) + ?: $from; + $senderName = $senderContactAlias + ?: $senderAlias + ?: $senderRawName; + $senderRole = (string)($row['sender_role'] ?? 'member'); + if (!in_array($senderRole, ['owner', 'admin'], true)) { + $senderRole = 'member'; + } + $timestamp = (int)$row['created_at_ms']; + $editedAt = (int)($row['edited_at_ms'] ?? 0); + $activityTimestamp = max($timestamp, $editedAt); + $direction = $from === $currentUserId ? 'out' : 'in'; + + $messageType = $attachment ? (string)$attachment['type'] : 'text'; + $contentType = $messageType === 'image' ? 3 : ($messageType === 'file' ? 5 : 1); + return [ + 'id' => (string)$row['message_uid'], + 'messageUid' => (string)$row['message_uid'], + 'conversationId' => 'group-' . $groupId, + 'from' => $from, + 'senderRole' => $senderRole, + 'senderName' => $senderName, + 'senderPortrait' => (string)($senderUser['_portrait'] ?? ''), + 'senderProfile' => [ + 'uid' => $from, + 'displayName' => $senderName, + 'rawName' => $senderRawName, + 'contactAlias' => $senderContactAlias, + 'friendAlias' => $senderFriendAlias, + 'remarkAlias' => $senderRemarkAlias, + 'groupAlias' => $senderAlias, + 'isContact' => (bool)($senderUser['_is_friend'] ?? false), + 'mobile' => (string)($senderUser['_mobile'] ?? ''), + 'address' => (string)($senderUser['_address'] ?? ''), + 'portrait' => (string)($senderUser['_portrait'] ?? ''), + 'role' => $senderRole, + ], + 'to' => $groupId, + 'target' => $groupId, + 'direction' => $direction, + 'type' => $messageType, + 'contentType' => $contentType, + 'text' => attachmentDisplayText((string)$row['text'], $attachment), + 'attachment' => $attachment, + 'status' => $direction === 'out' ? 'sent' : 'sent', + 'deliveryStatus' => $direction === 'out' ? 'sent' : '', + 'recalled' => false, + 'recallStatus' => '', + 'editedAt' => $editedAt, + 'timestamp' => $timestamp, + 'activityTimestamp' => $activityTimestamp, + 'displayTimestamp' => $timestamp, + 'conversationType' => (string)($group['category'] ?? 'group'), + ]; +} + +function loadWebGroupMessages(PDO $appDb, string $userId, string $groupId, int $limit, bool $markRead): array +{ + ensureWebImAppTables(); + $role = webGroupRole($appDb, $userId, $groupId); + if ($role === '') { + return []; + } + + $groupStatement = $appDb->prepare('select * from `t_web_groups` where `group_id` = ? and `deleted` = 0 limit 1'); + $groupStatement->execute([$groupId]); + $group = $groupStatement->fetch(); + if (!$group) { + return []; + } + + $statement = $appDb->prepare( + 'select msg.*,' + . ' coalesce(sender_member.`role`, if(g.`owner_uid` = msg.`sender_uid`, "owner", "member")) as `sender_role`,' + . ' coalesce(sender_member.`alias`, "") as `sender_alias`' + . ' from `t_web_group_messages` msg' + . ' join `t_web_groups` g on g.`group_id` = msg.`group_id` and g.`deleted` = 0' + . ' left join `t_web_group_members` sender_member' + . ' on sender_member.`group_id` = msg.`group_id`' + . ' and sender_member.`user_id` = msg.`sender_uid`' + . ' and sender_member.`status` = 0' + . ' where msg.`group_id` = ? and msg.`recalled` = 0' + . ' order by msg.`created_at_ms` desc, msg.`id` desc limit ' . $limit + ); + $statement->execute([$groupId]); + $rows = array_reverse($statement->fetchAll()); + $attachmentMap = fetchAttachmentMap($appDb, array_column($rows, 'message_uid')); + $users = fetchUserInfoMap(imDb(), array_column($rows, 'sender_uid'), $userId); + + $readTimestamp = 0; + $messages = []; + foreach ($rows as $row) { + $messageId = (string)$row['message_uid']; + $message = formatWebGroupMessage($row, $userId, $group, $attachmentMap[$messageId] ?? null, $users); + $readTimestamp = max($readTimestamp, (int)$message['activityTimestamp']); + $messages[] = $message; + } + + if ($markRead) { + markWebGroupRead($appDb, $userId, $groupId, $readTimestamp); + } + + return $messages; +} + +function loadWebGroupConversations(PDO $appDb, string $userId, int $limit): array +{ + ensureWebImAppTables(); + $statement = $appDb->prepare( + 'select g.*, m.`role`' + . ' from `t_web_groups` g' + . ' join `t_web_group_members` m on m.`group_id` = g.`group_id` and m.`status` = 0' + . ' where g.`deleted` = 0 and m.`user_id` = ?' + . ' order by g.`updated_at_ms` desc limit ' . min(max($limit, 50), 100) + ); + $statement->execute([$userId]); + $groups = $statement->fetchAll(); + if (!$groups) { + return []; + } + + $lastMessageStatement = $appDb->prepare( + 'select * from `t_web_group_messages`' + . ' where `group_id` = ? and `recalled` = 0' + . ' order by greatest(`created_at_ms`, `edited_at_ms`) desc, `id` desc limit 1' + ); + $readStatement = $appDb->prepare( + 'select `read_at_ms` from `t_web_group_reads` where `group_id` = ? and `user_id` = ? limit 1' + ); + $lastAttachmentStatement = $appDb->prepare( + 'select `attachment_type`, `url`, `name`, `mime`, `size_bytes`' + . ' from `t_web_message_attachments` where `message_uid` = ? limit 1' + ); + $unreadStatement = $appDb->prepare( + 'select count(*) from `t_web_group_messages`' + . ' where `group_id` = ? and `sender_uid` <> ? and `recalled` = 0' + . ' and greatest(`created_at_ms`, `edited_at_ms`) > ?' + ); + $memberCountStatement = $appDb->prepare( + 'select count(*) from `t_web_group_members` where `group_id` = ? and `status` = 0' + ); + + $conversations = []; + foreach ($groups as $group) { + $groupId = (string)$group['group_id']; + $lastMessageStatement->execute([$groupId]); + $lastMessage = $lastMessageStatement->fetch(); + $readStatement->execute([$groupId, $userId]); + $readAt = (int)($readStatement->fetchColumn() ?: 0); + $unreadStatement->execute([$groupId, $userId, $readAt]); + $unread = (int)$unreadStatement->fetchColumn(); + $memberCountStatement->execute([$groupId]); + $memberCount = (int)$memberCountStatement->fetchColumn(); + + $title = (string)$group['name']; + $category = (string)$group['category']; + $isChannel = $category === 'channel'; + $lastAttachment = null; + if ($lastMessage) { + $lastAttachmentStatement->execute([(string)$lastMessage['message_uid']]); + $lastAttachmentRow = $lastAttachmentStatement->fetch(); + $lastAttachment = $lastAttachmentRow ? normalizeAttachmentRow($lastAttachmentRow) : null; + } + $subtitle = $lastMessage + ? (messagePreviewText([ + 'text' => attachmentDisplayText((string)$lastMessage['text'], $lastAttachment), + 'attachment' => $lastAttachment, + ]) ?: (string)$lastMessage['text']) + : ($isChannel ? '公告群' : ($memberCount > 0 ? $memberCount . ' 位成员' : '群组')); + $updatedAt = $lastMessage + ? max((int)$lastMessage['created_at_ms'], (int)($lastMessage['edited_at_ms'] ?? 0)) + : (int)$group['updated_at_ms']; + + $conversations[] = [ + 'id' => 'group-' . $groupId, + 'type' => $isChannel ? 'channel' : 'group', + 'target' => $groupId, + 'title' => $title, + 'subtitle' => $subtitle, + 'unread' => $unread, + 'avatarText' => mb_strtoupper(mb_substr($title, 0, 1, 'UTF-8'), 'UTF-8'), + 'updatedAt' => $updatedAt, + 'role' => (string)$group['role'], + 'memberCount' => $memberCount, + 'raw' => [ + 'type' => 'group', + 'target' => $groupId, + 'line' => 0, + 'category' => $category, + 'role' => (string)$group['role'], + ], + ]; + } + + return $conversations; +} + +function isRecallMessage(array $row): bool +{ + return (int)($row['_content_type'] ?? 0) === 80; +} + +function shouldDisplayMessageForUser(array $row, string $currentUserId): bool +{ + if (!isRecallMessage($row)) { + return true; + } + + return (string)$row['_from'] === $currentUserId; +} + +function formatMessage( + array $row, + string $currentUserId, + string $peerId, + array $receiptState = [], + array $editState = [], + ?array $attachment = null +): array +{ + $messageId = (string)$row['_mid']; + $from = (string)$row['_from']; + $contentType = (int)($row['_content_type'] ?? 0); + $isRecalled = $contentType === 80; + $text = (string)($row['_searchable_key'] ?? ''); + $timestamp = timestampMs((string)($row['_dt'] ?? ''), $messageId); + $direction = $from === $currentUserId ? 'out' : 'in'; + $messageType = $attachment ? (string)$attachment['type'] : (($contentType === 1 || $isRecalled) ? 'text' : 'unknown'); + $messageText = $text !== '' ? $text : ($isRecalled || $attachment ? '' : '[暂不支持的消息类型]'); + $messageText = attachmentDisplayText($messageText, $attachment); + $editedAt = (int)($editState['editedAt'] ?? 0); + $activityTimestamp = messageActivityTimestamp($timestamp, $editState); + $deliveryStatus = $direction === 'out' && !$isRecalled + ? outgoingDeliveryStatus($activityTimestamp, $receiptState) + : ''; + + return [ + 'id' => $messageId, + 'messageUid' => $messageId, + 'conversationId' => 'single-' . $peerId, + 'from' => $from, + 'to' => $from === $currentUserId ? $peerId : $currentUserId, + 'target' => $peerId, + 'direction' => $direction, + 'type' => $messageType, + 'contentType' => $attachment ? ((string)$attachment['type'] === 'image' ? 3 : 5) : $contentType, + 'text' => $messageText, + 'attachment' => $attachment, + 'status' => $isRecalled && $direction === 'out' ? 'recalled' : ($direction === 'out' ? $deliveryStatus : 'sent'), + 'deliveryStatus' => $deliveryStatus, + 'recalled' => $isRecalled, + 'recallStatus' => $isRecalled ? '已撤回' : '', + 'editedAt' => $editedAt, + 'timestamp' => $timestamp, + 'activityTimestamp' => $activityTimestamp, + 'displayTimestamp' => $timestamp, + ]; +} + +function loadConversationMessages(PDO $db, string $userId, string $targetId, int $limit, bool $markRead): array +{ + $table = imUserMessagesTable($userId); + $conversationKeys = [$userId . '|' . $targetId, $targetId . '|' . $userId]; + $scanLimit = min(max($limit * 4, $limit), 400); + $statement = $db->prepare( + 'select `_mid`, `_dt` from `' . $table . '`' + . ' where `_uid` = ? and `_type` = 0 and `_line` = 0' + . ' and `_target` in (?, ?)' + . ' order by `_mid` desc limit ' . $scanLimit + ); + $statement->execute([$userId, $conversationKeys[0], $conversationKeys[1]]); + $userRows = $statement->fetchAll(); + $messageRows = fetchMessageRows($db, array_column($userRows, '_mid')); + $editMap = fetchMessageEditMap($db, array_column($userRows, '_mid')); + $attachmentMap = fetchAttachmentMap(imAppDb(), array_column($userRows, '_mid')); + $receiptState = loadPeerReceiptState($db, $userId, $targetId); + + $messages = []; + $seenMessageIds = []; + $readTimestamp = 0; + foreach ($userRows as $userRow) { + $messageId = (string)$userRow['_mid']; + if (isset($seenMessageIds[$messageId]) || !isset($messageRows[$messageId])) { + continue; + } + $seenMessageIds[$messageId] = true; + if (!shouldDisplayMessageForUser($messageRows[$messageId], $userId)) { + continue; + } + $message = formatMessage( + $messageRows[$messageId], + $userId, + $targetId, + $receiptState, + $editMap[$messageId] ?? [], + $attachmentMap[$messageId] ?? null + ); + $messages[] = $message; + $readTimestamp = max($readTimestamp, (int)$message['activityTimestamp']); + if (count($messages) >= $limit) { + break; + } + } + $messages = array_reverse($messages); + + if ($markRead) { + markConversationRead($db, $userId, $targetId, $readTimestamp); + } + + return $messages; +} + +function loadRecentConversations(PDO $db, string $userId, int $limit): array +{ + $table = imUserMessagesTable($userId); + $scanLimit = min(max($limit * 20, 200), 1000); + $statement = $db->prepare( + 'select `_mid`, `_target`, `_dt` from `' . $table . '`' + . ' where `_uid` = ? and `_type` = 0 and `_line` = 0' + . ' order by `_mid` desc limit ' . $scanLimit + ); + $statement->execute([$userId]); + + $scanRows = $statement->fetchAll(); + $messageRows = fetchMessageRows($db, array_column($scanRows, '_mid')); + $editMap = fetchMessageEditMap($db, array_column($scanRows, '_mid')); + $attachmentMap = fetchAttachmentMap(imAppDb(), array_column($scanRows, '_mid')); + $latestByPeer = []; + foreach ($scanRows as $row) { + $messageId = (string)$row['_mid']; + $message = $messageRows[$messageId] ?? null; + $peerId = peerFromConversationTarget($userId, (string)$row['_target']); + if ($peerId === '' || !$message) { + continue; + } + if (!shouldDisplayMessageForUser($message, $userId)) { + continue; + } + $activityTimestamp = messageActivityTimestamp( + timestampMs((string)($message['_dt'] ?? ''), $messageId), + $editMap[$messageId] ?? [] + ); + if (!isset($latestByPeer[$peerId]) || $activityTimestamp > (int)($latestByPeer[$peerId]['_activity_at'] ?? 0)) { + $row['_activity_at'] = $activityTimestamp; + $latestByPeer[$peerId] = $row; + } + } + uasort($latestByPeer, static fn($a, $b) => (int)($b['_activity_at'] ?? 0) <=> (int)($a['_activity_at'] ?? 0)); + $latestByPeer = array_slice($latestByPeer, 0, $limit, true); + + $users = fetchUserInfoMap($db, array_keys($latestByPeer), $userId); + $readAtByPeer = loadOwnReadStateMap($db, $userId, array_keys($latestByPeer)); + $unreadByPeer = []; + $seenUnreadMessageIds = []; + + foreach ($scanRows as $row) { + $messageId = (string)$row['_mid']; + if (isset($seenUnreadMessageIds[$messageId])) { + continue; + } + $seenUnreadMessageIds[$messageId] = true; + $peerId = peerFromConversationTarget($userId, (string)$row['_target']); + $message = $messageRows[$messageId] ?? null; + if ($peerId === '' || !$message || !isset($latestByPeer[$peerId])) { + continue; + } + if ((string)$message['_from'] === $userId) { + continue; + } + if (isRecallMessage($message)) { + continue; + } + $timestamp = timestampMs((string)($message['_dt'] ?? ''), $messageId); + $activityTimestamp = messageActivityTimestamp($timestamp, $editMap[$messageId] ?? []); + if ($activityTimestamp > (int)($readAtByPeer[$peerId] ?? 0)) { + $unreadByPeer[$peerId] = (int)($unreadByPeer[$peerId] ?? 0) + 1; + } + } + + $conversations = []; + + foreach ($latestByPeer as $peerId => $userRow) { + $messageId = (string)$userRow['_mid']; + $message = isset($messageRows[$messageId]) + ? formatMessage($messageRows[$messageId], $userId, $peerId, [], $editMap[$messageId] ?? [], $attachmentMap[$messageId] ?? null) + : null; + $title = userTitle($users, $peerId); + $conversations[] = [ + 'id' => 'single-' . $peerId, + 'type' => 'single', + 'target' => $peerId, + 'title' => $title, + 'subtitle' => $message ? (messagePreviewText($message) !== '' ? messagePreviewText($message) : (empty($message['recalled']) ? '' : '已撤回')) : '', + 'unread' => (int)($unreadByPeer[$peerId] ?? 0), + 'portrait' => (string)($users[$peerId]['_portrait'] ?? ''), + 'avatarText' => mb_strtoupper(mb_substr($title, 0, 1, 'UTF-8'), 'UTF-8'), + 'updatedAt' => $message ? $message['activityTimestamp'] : timestampMs((string)($userRow['_dt'] ?? ''), $messageId), + 'raw' => ['type' => 'single', 'target' => $peerId, 'line' => 0], + ]; + } + + try { + $conversations = array_merge($conversations, loadWebGroupConversations(imAppDb(), $userId, $limit)); + } catch (Throwable) { + // Keep single-chat sync available if appdata group metadata is temporarily unavailable. + } + + usort($conversations, static fn($a, $b) => $b['updatedAt'] <=> $a['updatedAt']); + + return array_slice($conversations, 0, $limit); +} + +$payload = readJsonPayload(); +$userId = trim((string)($payload['userId'] ?? '')); +$target = trim((string)($payload['target'] ?? '')); +$limit = clampLimit($payload['limit'] ?? 50, 50, 100); +$markRead = payloadBool($payload['markRead'] ?? false); +$conversationType = normalizeConversationType($payload['conversationType'] ?? $payload['type'] ?? 0); + +if ($userId === '') { + respond(400, '缺少用户ID'); +} + +try { + $db = imDb(); + $userId = resolveUserId($db, $userId); + markUserDelivered($db, $userId, nowMs()); + + if ($target !== '') { + $targetId = resolveUserId($db, $target); + if ($conversationType === 'group' || isWebGroupTarget(imAppDb(), $target)) { + respond(0, 'success', [ + 'messages' => loadWebGroupMessages(imAppDb(), $userId, $target, $limit, $markRead), + ]); + } + respond(0, 'success', [ + 'messages' => loadConversationMessages($db, $userId, $targetId, $limit, $markRead), + ]); + } + + respond(0, 'success', [ + 'conversations' => loadRecentConversations($db, $userId, $limit), + ]); +} catch (Throwable $error) { + respond(500, $error->getMessage() ?: '消息同步失败'); +} diff --git a/dist/api/platform_config.php b/dist/api/platform_config.php new file mode 100644 index 0000000..af63018 --- /dev/null +++ b/dist/api/platform_config.php @@ -0,0 +1,39 @@ +prepare( + 'select `conf_name`, `conf_value`, `updated_at` from `system_conf` where `conf_name` in (?, ?)' + ); + $statement->execute(['PlatformName', 'sys_logo']); + + $config = []; + $updatedAtValues = []; + foreach ($statement->fetchAll() as $row) { + $config[(string)$row['conf_name']] = trim((string)($row['conf_value'] ?? '')); + $updatedAtValues[] = (string)($row['updated_at'] ?? ''); + } + + $platformName = $config['PlatformName'] ?? ''; + $logo = $config['sys_logo'] ?? ''; + $version = implode('|', array_values(array_filter($updatedAtValues ?? []))); + + respond(0, 'success', [ + 'platformName' => $platformName !== '' ? $platformName : $defaultName, + 'logo' => $logo !== '' ? $logo : $defaultLogo, + 'version' => $version, + ]); +} catch (Throwable $error) { + respond(500, $error->getMessage() ?: '平台配置加载失败', [ + 'platformName' => $defaultName, + 'logo' => $defaultLogo, + ]); +} diff --git a/dist/api/profile.php b/dist/api/profile.php new file mode 100644 index 0000000..cb3a75e --- /dev/null +++ b/dist/api/profile.php @@ -0,0 +1,321 @@ + $uid, + 'userId' => $uid, + 'name' => (string)($user['name'] ?? $user['_name'] ?? ''), + 'displayName' => (string)($user['displayName'] ?? $user['_display_name'] ?? $user['name'] ?? $user['_name'] ?? $uid), + 'mobile' => (string)($user['mobile'] ?? $user['_mobile'] ?? ''), + 'portrait' => (string)($user['portrait'] ?? $user['_portrait'] ?? ''), + 'address' => (string)($user['address'] ?? $user['_address'] ?? ''), + ]; +} + +function tryProfileUserInfo(array $payload): ?array +{ + try { + $result = requireImSuccess(imAdminPost('/admin/user/get_info', $payload), '用户资料加载失败'); + return $result ?: null; + } catch (Throwable) { + return null; + } +} + +function loadProfileUserFromDb(string $identity): ?array +{ + $statement = imDb()->prepare( + 'select `_uid`, `_name`, `_display_name`, `_mobile`, `_portrait`, `_address`' + . ' from `t_user` where `_uid` = ? or `_mobile` = ? or `_name` = ?' + . ' order by case when `_uid` = ? then 0 when `_mobile` = ? then 1 when `_name` = ? then 2 else 3 end' + . ' limit 1' + ); + $statement->execute([$identity, $identity, $identity, $identity, $identity, $identity]); + $row = $statement->fetch(); + return $row ? normalizeProfileUser($row) : null; +} + +function mergeProfileUser(array $primary, ?array $fallback): array +{ + if (!$fallback) { + return $primary; + } + + $fallback = normalizeProfileUser($fallback); + foreach (['name', 'displayName', 'mobile', 'portrait', 'address'] as $key) { + if ((string)($primary[$key] ?? '') === '' && (string)($fallback[$key] ?? '') !== '') { + $primary[$key] = (string)$fallback[$key]; + } + } + + return $primary; +} + +function persistProfileUserToDb(string $userId, string $displayName, string $portrait): void +{ + if ($portrait !== '') { + $statement = imDb()->prepare( + 'update `t_user` set `_display_name` = ?, `_portrait` = ?, `_dt` = ? where `_uid` = ? limit 1' + ); + $statement->execute([$displayName, $portrait, webImNowMs(), $userId]); + return; + } + + $statement = imDb()->prepare( + 'update `t_user` set `_display_name` = ?, `_dt` = ? where `_uid` = ? limit 1' + ); + $statement->execute([$displayName, webImNowMs(), $userId]); +} + +function loadProfileUser(string $identity, bool $preferMobile = true): array +{ + $identity = trim($identity); + if ($identity === '') { + respond(400, '缺少用户ID'); + } + + $dbUser = loadProfileUserFromDb($identity); + if ($dbUser) { + $fallback = null; + if ((string)($dbUser['portrait'] ?? '') === '') { + $fallback = tryProfileUserInfo(['userId' => $dbUser['userId']]); + if (!$fallback && (string)($dbUser['mobile'] ?? '') !== '') { + $fallback = tryProfileUserInfo(['mobile' => $dbUser['mobile']]); + } + if (!$fallback && (string)($dbUser['name'] ?? '') !== '') { + $fallback = tryProfileUserInfo(['name' => $dbUser['name']]); + } + } + + return mergeProfileUser($dbUser, $fallback); + } + + $user = null; + if ($preferMobile && preg_match('/^\+?\d{5,32}$/', $identity)) { + $user = tryProfileUserInfo(['mobile' => $identity]); + } + if (!$user) { + $user = tryProfileUserInfo(['userId' => $identity]); + } + if (!$user) { + $user = tryProfileUserInfo(['name' => $identity]); + } + if (!$user) { + respond(404, '用户资料不存在'); + } + + return normalizeProfileUser($user); +} + +function readProfileJavaSerializedString(string $data, int $offset): ?array +{ + if ($offset + 3 > strlen($data) || ord($data[$offset]) !== 0x74) { + return null; + } + + $length = (ord($data[$offset + 1]) << 8) | ord($data[$offset + 2]); + $start = $offset + 3; + if ($start + $length > strlen($data)) { + return null; + } + + return [substr($data, $start, $length), $start + $length]; +} + +function profileSessionUserId(string $sessionData): string +{ + $marker = "\x74\x00\x06userId"; + $offset = strpos($sessionData, $marker); + if ($offset === false) { + return ''; + } + + $decoded = readProfileJavaSerializedString($sessionData, $offset + strlen($marker)); + $userId = is_array($decoded) ? (string)$decoded[0] : ''; + if (!preg_match('/^[A-Za-z0-9_:@.+-]{1,128}$/', $userId)) { + return ''; + } + + return $userId; +} + +function requireProfileUpdateAuth(string $targetUserId): void +{ + $authToken = trim((string)($_SERVER['HTTP_AUTHTOKEN'] ?? $_SERVER['HTTP_AUTH_TOKEN'] ?? '')); + if ($authToken === '') { + respond(401, '请重新登录后再修改资料'); + } + + $statement = imAppDb()->prepare('select `session_data` from `shiro_session` where `session_id` = ? limit 1'); + $statement->execute([$authToken]); + $row = $statement->fetch(); + if (!$row) { + respond(401, '登录状态已失效,请重新登录'); + } + + $sessionUserId = profileSessionUserId((string)$row['session_data']); + if ($sessionUserId === '' || $sessionUserId !== $targetUserId) { + respond(403, '只能修改自己的资料'); + } +} + +function profileUploadedFileExtension(string $fileName, string $mime): string +{ + $extension = strtolower((string)pathinfo($fileName, PATHINFO_EXTENSION)); + $extension = preg_replace('/[^a-z0-9]/', '', $extension) ?: ''; + if ($extension === 'jpeg') { + $extension = 'jpg'; + } + + $allowedExtensions = ['jpg', 'png', 'gif', 'webp', 'bmp', 'ico']; + if (in_array($extension, $allowedExtensions, true)) { + return $extension; + } + + return match ($mime) { + 'image/jpeg' => 'jpg', + 'image/png' => 'png', + 'image/gif' => 'gif', + 'image/webp' => 'webp', + 'image/bmp', 'image/x-ms-bmp' => 'bmp', + 'image/x-icon', 'image/vnd.microsoft.icon' => 'ico', + default => 'jpg', + }; +} + +function profilePublicMediaBaseUrl(): string +{ + $isHttps = (string)($_SERVER['HTTPS'] ?? '') !== '' && strtolower((string)$_SERVER['HTTPS']) !== 'off'; + $scheme = $isHttps ? 'https' : 'http'; + $host = (string)($_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? '127.0.0.1'); + $host = trim(preg_replace('/:\d+$/', '', $host) ?: '127.0.0.1'); + + return $scheme . '://' . $host . ':' . IM_LOCAL_MEDIA_PUBLIC_PORT; +} + +function uploadProfilePortrait(array $file): string +{ + if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) { + throw new RuntimeException('头像上传失败'); + } + if (($file['size'] ?? 0) <= 0 || ($file['size'] ?? 0) > 5 * 1024 * 1024) { + throw new RuntimeException('头像不能超过5MB'); + } + + $tmpName = (string)($file['tmp_name'] ?? ''); + $mime = mime_content_type($tmpName) ?: ''; + if (!str_starts_with($mime, 'image/')) { + throw new RuntimeException('头像必须是图片文件'); + } + + if (!is_dir(IM_LOCAL_MEDIA_ROOT) || !is_writable(IM_LOCAL_MEDIA_ROOT)) { + throw new RuntimeException('头像存储目录不可写'); + } + + $datePath = 'fs/' . PROFILE_PORTRAIT_MEDIA_TYPE . '/' . date('Y/m/d/H/i/s'); + $targetDir = rtrim(IM_LOCAL_MEDIA_ROOT, '/') . '/' . $datePath; + if (!is_dir($targetDir) && !mkdir($targetDir, 0775, true)) { + throw new RuntimeException('头像存储目录创建失败'); + } + + $extension = profileUploadedFileExtension((string)($file['name'] ?? 'avatar.jpg'), $mime); + $targetName = bin2hex(random_bytes(12)) . '.' . $extension; + $targetPath = $targetDir . '/' . $targetName; + if (!move_uploaded_file($tmpName, $targetPath)) { + throw new RuntimeException('头像文件保存失败'); + } + @chmod($targetPath, 0644); + + return profilePublicMediaBaseUrl() . '/' . $datePath . '/' . $targetName; +} + +try { + $contentType = (string)($_SERVER['CONTENT_TYPE'] ?? ''); + $isMultipart = stripos($contentType, 'multipart/form-data') !== false; + $payload = $isMultipart ? [] : readJsonPayload(); + $action = $isMultipart + ? profileRequestValue('action', 'update') + : trim((string)($payload['action'] ?? 'get')); + $userId = $isMultipart + ? profileRequestValue('userId') + : trim((string)($payload['userId'] ?? $_GET['userId'] ?? '')); + + $currentProfile = loadProfileUser($userId); + + if ($action === 'get') { + respond(0, 'success', ['profile' => $currentProfile]); + } + + if ($action !== 'update') { + respond(400, '不支持的资料操作'); + } + requireProfileUpdateAuth((string)$currentProfile['userId']); + + $displayName = $isMultipart + ? profileRequestValue('displayName') + : trim((string)($payload['displayName'] ?? '')); + if ($displayName === '') { + respond(400, '昵称不能为空'); + } + if (mb_strlen($displayName, 'UTF-8') > 64) { + respond(400, '昵称不能超过64个字'); + } + + $portrait = ''; + if (!empty($_FILES['portraitFile']) && ($_FILES['portraitFile']['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_NO_FILE) { + $portrait = uploadProfilePortrait($_FILES['portraitFile']); + } + + $flag = PROFILE_DISPLAY_NAME_FLAG; + $userInfo = [ + 'userId' => $currentProfile['userId'], + 'displayName' => $displayName, + ]; + if ($portrait !== '') { + $flag |= PROFILE_PORTRAIT_FLAG; + $userInfo['portrait'] = $portrait; + } + + requireImSuccess( + imAdminPost('/admin/user/update', [ + 'flag' => $flag, + 'userInfo' => $userInfo, + ]), + '用户资料保存失败' + ); + persistProfileUserToDb($currentProfile['userId'], $displayName, $portrait); + + $updatedProfile = loadProfileUser($currentProfile['userId'], false); + if ($portrait === '' && (string)$updatedProfile['portrait'] === '' && (string)($currentProfile['portrait'] ?? '') !== '') { + $updatedProfile['portrait'] = (string)$currentProfile['portrait']; + } + if ($portrait !== '' && (string)$updatedProfile['portrait'] === '') { + $updatedProfile['portrait'] = $portrait; + } + if ((string)$updatedProfile['displayName'] === '') { + $updatedProfile['displayName'] = $displayName; + } + + respond(0, 'success', ['profile' => $updatedProfile]); +} catch (Throwable $error) { + respond(500, $error->getMessage() ?: '用户资料服务暂不可用'); +} diff --git a/dist/api/register_config.php b/dist/api/register_config.php new file mode 100644 index 0000000..887de79 --- /dev/null +++ b/dist/api/register_config.php @@ -0,0 +1,18 @@ + 0, + 'message' => 'success', + 'data' => [ + 'methods' => [ + 'phone' => true, + 'email' => false, + 'username' => false, + ], + 'mobile_real_verify' => false, + ], +], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); diff --git a/dist/api/send_message.php b/dist/api/send_message.php new file mode 100644 index 0000000..c06af18 --- /dev/null +++ b/dist/api/send_message.php @@ -0,0 +1,411 @@ +setTimezone(new DateTimeZone(IM_DB_TIMEZONE)); + $currentMinutes = ((int)$now->format('H')) * 60 + (int)$now->format('i'); + [$startHour, $startMinute] = array_map('intval', explode(':', $dailyStart)); + [$endHour, $endMinute] = array_map('intval', explode(':', $dailyEnd)); + $startMinutes = $startHour * 60 + $startMinute; + $endMinutes = $endHour * 60 + $endMinute; + if ($startMinutes === $endMinutes) { + return true; + } + if ($startMinutes < $endMinutes) { + return $currentMinutes >= $startMinutes && $currentMinutes < $endMinutes; + } + + return $currentMinutes >= $startMinutes || $currentMinutes < $endMinutes; +} + +function isGroupMutedForSender(PDO $appDb, string $groupId, string $role, int $nowMs): bool +{ + if ($role === 'owner') { + return false; + } + + $statement = $appDb->prepare('select * from `t_web_group_mute_rules` where `group_id` = ? and `enabled` = 1 limit 1'); + $statement->execute([$groupId]); + $rule = $statement->fetch(); + if (!is_array($rule)) { + return false; + } + + $mode = (string)$rule['mode']; + if ($mode === 'off') { + return false; + } + if ($mode === 'all_except_admins' && $role === 'admin') { + return false; + } + if (!in_array($mode, ['all_include_admins', 'all_except_admins'], true)) { + return false; + } + + $scheduleType = (string)$rule['schedule_type']; + if ($scheduleType === 'manual') { + return true; + } + if ($scheduleType === 'daily') { + return isDailyMuteActive((string)$rule['daily_start'], (string)$rule['daily_end'], $nowMs); + } + + $startsAtMs = (int)$rule['starts_at_ms']; + $endsAtMs = (int)$rule['ends_at_ms']; + return $startsAtMs > 0 && $endsAtMs > $startsAtMs && $nowMs >= $startsAtMs && $nowMs < $endsAtMs; +} + +function messageRequestValue(array $payload, string $key, string $default = ''): string +{ + return trim((string)($_POST[$key] ?? $payload[$key] ?? $default)); +} + +function safeUploadName(string $fileName): string +{ + $name = basename(str_replace('\\', '/', $fileName)); + $name = preg_replace('/[^\p{L}\p{N}._ -]+/u', '_', $name) ?: 'file'; + $name = trim($name, " .\t\n\r\0\x0B"); + + return $name !== '' ? mb_substr($name, 0, 180, 'UTF-8') : 'file'; +} + +function uploadExtension(string $fileName, string $mime, string $attachmentType): string +{ + $extension = strtolower((string)pathinfo($fileName, PATHINFO_EXTENSION)); + $extension = preg_replace('/[^a-z0-9]/', '', $extension) ?: ''; + if ($extension === 'jpeg') { + $extension = 'jpg'; + } + + if ($attachmentType === 'image') { + if (in_array($extension, ['jpg', 'png', 'gif', 'webp', 'bmp', 'svg', 'avif', 'heic', 'heif'], true)) { + return $extension; + } + return match ($mime) { + 'image/jpeg' => 'jpg', + 'image/png' => 'png', + 'image/gif' => 'gif', + 'image/webp' => 'webp', + 'image/bmp', 'image/x-ms-bmp' => 'bmp', + 'image/svg+xml' => 'svg', + 'image/avif' => 'avif', + 'image/heic' => 'heic', + 'image/heif' => 'heif', + default => 'jpg', + }; + } + + if ($extension !== '') { + return $extension; + } + + return 'bin'; +} + +function detectedUploadMime(string $tmpName): string +{ + $mime = mime_content_type($tmpName) ?: ''; + if ($mime !== '' && $mime !== 'application/octet-stream') { + return $mime; + } + + $imageInfo = @getimagesize($tmpName); + if (is_array($imageInfo) && !empty($imageInfo['mime'])) { + return (string)$imageInfo['mime']; + } + + return $mime !== '' ? $mime : 'application/octet-stream'; +} + +function normalizedImageUploadMime(string $tmpName, string $mime): string +{ + if (str_starts_with($mime, 'image/')) { + return $mime; + } + + $imageInfo = @getimagesize($tmpName); + $imageMime = is_array($imageInfo) ? (string)($imageInfo['mime'] ?? '') : ''; + if ($imageMime !== '' && str_starts_with($imageMime, 'image/')) { + return $imageMime; + } + + throw new RuntimeException('请选择图片文件'); +} + +function mediaPublicBaseUrl(): string +{ + $isHttps = (string)($_SERVER['HTTPS'] ?? '') !== '' && strtolower((string)$_SERVER['HTTPS']) !== 'off'; + $scheme = $isHttps ? 'https' : 'http'; + $host = trim(preg_replace('/:\d+$/', '', (string)($_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? '127.0.0.1')) ?: '127.0.0.1'); + + return $scheme . '://' . $host . ':' . WEB_MEDIA_PUBLIC_PORT; +} + +function saveMessageAttachmentFile(array $file, string $attachmentType): array +{ + if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) { + throw new RuntimeException('文件上传失败'); + } + $size = (int)($file['size'] ?? 0); + $maxSize = $attachmentType === 'image' ? WEB_IMAGE_MAX_BYTES : WEB_FILE_MAX_BYTES; + if ($size <= 0 || $size > $maxSize) { + throw new RuntimeException($attachmentType === 'image' ? '图片不能超过10MB' : '文件不能超过50MB'); + } + if (!is_dir(WEB_MEDIA_ROOT) || !is_writable(WEB_MEDIA_ROOT)) { + throw new RuntimeException('媒体存储目录不可写'); + } + + $tmpName = (string)($file['tmp_name'] ?? ''); + if ($tmpName === '' || !is_uploaded_file($tmpName)) { + throw new RuntimeException('文件上传失败'); + } + $mime = detectedUploadMime($tmpName); + if ($attachmentType === 'image') { + $mime = normalizedImageUploadMime($tmpName, $mime); + } + + $mediaType = $attachmentType === 'image' ? WEB_MEDIA_TYPE_IMAGE : WEB_MEDIA_TYPE_FILE; + $datePath = 'fs/' . $mediaType . '/' . date('Y/m/d/H/i/s'); + $targetDir = rtrim(WEB_MEDIA_ROOT, '/') . '/' . $datePath; + if (!is_dir($targetDir) && !mkdir($targetDir, 0775, true)) { + throw new RuntimeException('媒体存储目录创建失败'); + } + + $originalName = safeUploadName((string)($file['name'] ?? ($attachmentType === 'image' ? 'image.jpg' : 'file'))); + $extension = uploadExtension($originalName, $mime, $attachmentType); + $targetName = bin2hex(random_bytes(12)) . '.' . $extension; + $targetPath = $targetDir . '/' . $targetName; + if (!move_uploaded_file($tmpName, $targetPath)) { + throw new RuntimeException('文件保存失败'); + } + @chmod($targetPath, 0644); + + return [ + 'type' => $attachmentType, + 'url' => mediaPublicBaseUrl() . '/' . $datePath . '/' . $targetName, + 'name' => $originalName, + 'mime' => $mime, + 'size' => $size, + 'mediaType' => $mediaType, + ]; +} + +function storeMessageAttachment( + PDO $appDb, + string $messageUid, + string $conversationType, + string $target, + string $sender, + array $attachment, + int $timestamp +): void { + if ($messageUid === '' || empty($attachment['url'])) { + return; + } + + $statement = $appDb->prepare( + 'insert into `t_web_message_attachments`' + . ' (`message_uid`, `conversation_type`, `target_id`, `sender_uid`, `attachment_type`, `url`, `name`, `mime`, `size_bytes`, `created_at_ms`)' + . ' values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)' + . ' on duplicate key update' + . ' `attachment_type` = values(`attachment_type`),' + . ' `url` = values(`url`),' + . ' `name` = values(`name`),' + . ' `mime` = values(`mime`),' + . ' `size_bytes` = values(`size_bytes`)' + ); + $statement->execute([ + $messageUid, + $conversationType, + $target, + $sender, + (string)$attachment['type'], + (string)$attachment['url'], + (string)$attachment['name'], + (string)$attachment['mime'], + (int)$attachment['size'], + $timestamp, + ]); +} + +function attachmentFromRequest(string $messageType): ?array +{ + if (!in_array($messageType, ['image', 'file'], true)) { + return null; + } + + $attachmentFile = $_FILES['attachment'] ?? $_FILES['file'] ?? null; + if (!is_array($attachmentFile)) { + respond(400, '缺少上传文件'); + } + + return saveMessageAttachmentFile($attachmentFile, $messageType); +} + +$contentTypeHeader = (string)($_SERVER['CONTENT_TYPE'] ?? ''); +$isMultipart = stripos($contentTypeHeader, 'multipart/form-data') !== false; +$payload = $isMultipart ? [] : readJsonPayload(); +$sender = messageRequestValue($payload, 'sender'); +$target = messageRequestValue($payload, 'target'); +$text = messageRequestValue($payload, 'text'); +$messageType = messageRequestValue($payload, 'messageType', 'text'); +$conversationType = $_POST['conversationType'] ?? $_POST['type'] ?? $payload['conversationType'] ?? $payload['type'] ?? 0; +$isGroupConversation = in_array((string)$conversationType, ['1', 'group', 'channel'], true); +$attachment = null; + +if ($sender === '' || $target === '' || ($text === '' && !in_array($messageType, ['image', 'file'], true))) { + respond(400, '缺少发送者、接收者或消息内容'); +} + +try { + if ($isGroupConversation) { + ensureWebImAppTables(); + $appDb = imAppDb(); + $memberStatement = $appDb->prepare( + 'select g.`group_id`, g.`category`, g.`owner_uid`, m.`role`' + . ' from `t_web_groups` g' + . ' join `t_web_group_members` m on m.`group_id` = g.`group_id` and m.`status` = 0' + . ' where g.`deleted` = 0 and g.`group_id` = ? and m.`user_id` = ?' + . ' limit 1' + ); + $memberStatement->execute([$target, $sender]); + $group = $memberStatement->fetch(); + if (!$group) { + respond(403, '你不在该群组中'); + } + $category = (string)($group['category'] ?? 'group'); + $role = (string)($group['role'] ?? 'member'); + if ($category === 'channel' && !in_array($role, ['owner', 'admin'], true)) { + respond(403, '公告群仅管理员可发言'); + } + + $now = webImNowMs(); + if ($category !== 'channel' && isGroupMutedForSender($appDb, $target, $role, $now)) { + respond(403, '当前群组处于禁言时段'); + } + $attachment = attachmentFromRequest($messageType); + $messageUid = webImMessageUid(); + $insert = $appDb->prepare( + 'insert into `t_web_group_messages`' + . ' (`message_uid`, `group_id`, `sender_uid`, `text`, `created_at_ms`, `edited_at_ms`, `recalled`)' + . ' values (?, ?, ?, ?, ?, 0, 0)' + ); + $insert->execute([$messageUid, $target, $sender, $text, $now]); + if ($attachment) { + storeMessageAttachment($appDb, $messageUid, 'group', $target, $sender, $attachment, $now); + } + $updateGroup = $appDb->prepare('update `t_web_groups` set `updated_at_ms` = ? where `group_id` = ?'); + $updateGroup->execute([$now, $target]); + + $nativeError = ''; + try { + $pushContent = $text !== '' + ? $text + : ($attachment + ? ((string)$attachment['type'] === 'image' ? '[图片]' : '[文件] ' . (string)$attachment['name']) + : $text); + $payloadBody = [ + 'type' => $attachment ? ((string)$attachment['type'] === 'image' ? 3 : 5) : 1, + 'searchableContent' => $pushContent, + 'pushContent' => $pushContent, + 'content' => $text, + 'persistFlag' => 3, + ]; + if ($attachment) { + $payloadBody['mediaType'] = (int)$attachment['mediaType']; + $payloadBody['remoteMediaUrl'] = (string)$attachment['url']; + $payloadBody['extra'] = json_encode([ + 'name' => (string)$attachment['name'], + 'mime' => (string)$attachment['mime'], + 'size' => (int)$attachment['size'], + ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + } + requireImSuccess(imAdminPost('/admin/message/send', [ + 'sender' => $sender, + 'conv' => [ + 'type' => 1, + 'target' => $target, + 'line' => 0, + ], + 'payload' => $payloadBody, + 'userMessage' => true, + ]), '原生群消息发送失败'); + } catch (Throwable $error) { + $nativeError = $error->getMessage() ?: '原生群消息发送失败'; + } + + respond(0, 'success', [ + 'messageUid' => $messageUid, + 'timestamp' => $now, + 'senderRole' => $role, + 'attachment' => $attachment, + 'nativeError' => $nativeError, + ]); + } + + $attachment = attachmentFromRequest($messageType); + $pushContent = $text !== '' + ? $text + : ($attachment + ? ((string)$attachment['type'] === 'image' ? '[图片]' : '[文件] ' . (string)$attachment['name']) + : $text); + $payloadBody = [ + 'type' => $attachment ? ((string)$attachment['type'] === 'image' ? 3 : 5) : 1, + 'searchableContent' => $pushContent, + 'pushContent' => $pushContent, + 'content' => $text, + 'persistFlag' => 3, + ]; + if ($attachment) { + $payloadBody['mediaType'] = (int)$attachment['mediaType']; + $payloadBody['remoteMediaUrl'] = (string)$attachment['url']; + $payloadBody['extra'] = json_encode([ + 'name' => (string)$attachment['name'], + 'mime' => (string)$attachment['mime'], + 'size' => (int)$attachment['size'], + ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + } + + $result = requireImSuccess(imAdminPost('/admin/message/send', [ + 'sender' => $sender, + 'conv' => [ + 'type' => 0, + 'target' => $target, + 'line' => 0, + ], + 'payload' => $payloadBody, + 'userMessage' => true, + ]), '发送失败'); + + $messageUid = isset($result['messageUid']) ? (string)$result['messageUid'] : ''; + $timestamp = (int)($result['timestamp'] ?? webImNowMs()); + if ($attachment && $messageUid !== '') { + ensureWebImAppTables(); + storeMessageAttachment(imAppDb(), $messageUid, 'single', $target, $sender, $attachment, $timestamp); + } + + respond(0, 'success', [ + 'messageUid' => $messageUid !== '' ? $messageUid : null, + 'timestamp' => $timestamp, + 'attachment' => $attachment, + ]); +} catch (Throwable $error) { + respond(500, $error->getMessage() ?: '发送失败'); +} diff --git a/dist/api/update_alias.php b/dist/api/update_alias.php new file mode 100644 index 0000000..7f3be6c --- /dev/null +++ b/dist/api/update_alias.php @@ -0,0 +1,61 @@ + 32) { + respond(400, '备注名不能超过32个字'); +} + +try { + ensureWebImAppTables(); + $imDb = imDb(); + $exists = $imDb->prepare('select 1 from `t_user` where `_uid` = ? limit 1'); + $exists->execute([$targetId]); + if (!$exists->fetchColumn()) { + respond(404, '没有找到该用户'); + } + + $friend = $imDb->prepare('select 1 from `t_friend` where `_uid` = ? and `_friend_uid` = ? limit 1'); + $friend->execute([$userId, $targetId]); + $isContact = (bool)$friend->fetchColumn(); + + if ($isContact) { + requireImSuccess( + imAdminPost('/admin/friend/set_alias', [ + 'operator' => $userId, + 'targetId' => $targetId, + 'alias' => $alias, + ]), + '备注名修改失败' + ); + } + saveWebUserRemark(imAppDb(), $userId, $targetId, $alias); + + respond(0, 'success', [ + 'contact' => [ + 'uid' => $targetId, + 'alias' => $alias, + 'isContact' => $isContact, + ], + ]); +} catch (Throwable $error) { + respond(500, $error->getMessage() ?: '备注名服务暂不可用'); +} diff --git a/dist/config/runtime-config.js b/dist/config/runtime-config.js new file mode 100644 index 0000000..2728254 --- /dev/null +++ b/dist/config/runtime-config.js @@ -0,0 +1,12 @@ +window.JC_WEB_IM_CONFIG = { + adminServer: 'http://winchen.ezpay.asia:8024', + appServer: 'http://winchen.ezpay.asia:8888', + qrAppServer: 'http://winchen.ezpay.asia:18888', + platformConfigApi: '/api/platform_config.php', + registerConfigApi: 'http://winchen.ezpay.asia:8024/api/register-config', + imServerHost: 'winchen.ezpay.asia', + imRoutePort: 18000, + useWss: false, + wsPath: '/ws', + sdkMode: 'http' +}; diff --git a/dist/css/app.5af3ef7e.css b/dist/css/app.5af3ef7e.css new file mode 100644 index 0000000..753c543 --- /dev/null +++ b/dist/css/app.5af3ef7e.css @@ -0,0 +1,3048 @@ +:root { + color-scheme: light; + --bg: #f4f6f8; + --panel: #ffffff; + --panel-soft: #f8fafc; + --line: #d9e0e7; + --text: #18212f; + --muted: #6a7584; + --primary: #177d72; + --primary-strong: #0f625a; + --accent: #d84e3f; + --bubble-in: #ffffff; + --bubble-out: #dff4ef; + --rail: #202a33; +} + +* { + box-sizing: border-box; +} + +html, +body, +#app { + width: 100%; + height: 100%; + overflow: hidden; +} + +body { + margin: 0; + min-width: 320px; + background: var(--bg); + color: var(--text); + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; +} + +button, +input, +textarea { + font: inherit; +} + +button { + cursor: pointer; +} + +.login-page { + min-height: 100vh; + display: grid; + place-items: center; + padding: 24px; + background: linear-gradient(135deg, #eef6f3 0%, #f7f3ee 55%, #eef2f7 100%); +} + +.login-panel { + width: min(420px, 100%); + padding: 32px; + background: var(--panel); + border: 1px solid var(--line); + border-radius: 8px; + box-shadow: 0 18px 48px rgba(30, 40, 50, 0.12); +} + +.brand-lockup { + display: flex; + align-items: center; + gap: 14px; + margin-bottom: 28px; +} + +.brand-mark, +.rail-avatar, +.conversation-avatar { + display: grid; + place-items: center; + flex: 0 0 auto; +} + +.image-avatar { + -o-object-fit: cover; + object-fit: cover; +} + +.brand-mark { + width: 48px; + height: 48px; + border-radius: 8px; + -o-object-fit: contain; + object-fit: contain; + background: #fff; +} + +.brand-lockup h1, +.list-header h2, +.chat-header h2 { + margin: 0; + font-size: 20px; +} + +.brand-lockup p, +.list-header p, +.chat-header p { + margin: 4px 0 0; + color: var(--muted); + font-size: 13px; +} + +.login-form { + display: grid; + gap: 16px; +} + +.login-tabs, +.code-row, +.phone-row, +.captcha-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; +} + +.login-tabs button, +.code-action { + height: 38px; + border: 1px solid var(--line); + border-radius: 6px; + background: #fff; + color: var(--text); +} + +.login-tabs button.active { + border-color: var(--primary); + background: #e8f4f2; + color: var(--primary-strong); + font-weight: 700; +} + +.login-tabs { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.login-tabs button { + min-width: 0; + padding: 0 6px; + overflow-wrap: anywhere; +} + +.code-row { + grid-template-columns: minmax(0, 1fr) 112px; +} + +.captcha-row { + grid-template-columns: minmax(0, 1fr) 112px; +} + +.phone-row { + grid-template-columns: 86px minmax(0, 1fr); +} + +.country-code-field { + height: 42px; + display: grid; + grid-template-columns: 24px minmax(0, 1fr); + align-items: center; + border: 1px solid var(--line); + border-radius: 6px; + background: #fff; + color: var(--text); + overflow: hidden; +} + +.country-code-field:focus-within { + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(23, 125, 114, 0.12); +} + +.country-code-field span { + display: grid; + place-items: center; + color: var(--muted); + font-weight: 700; +} + +.code-action:disabled { + cursor: not-allowed; + opacity: 0.55; +} + +.login-form label { + display: grid; + gap: 8px; + color: var(--muted); + font-size: 13px; +} + +.label-row { + min-height: 18px; + display: grid; + grid-template-columns: 86px minmax(0, 1fr); + align-items: center; + gap: 8px; +} + +.field-error-badge { + min-height: 28px; + display: grid; + place-items: center; + padding: 4px 10px; + border-radius: 2px; + background: var(--accent); + color: #fff; + font-weight: 700; + line-height: 1.2; + text-align: center; +} + +.login-form input, +.login-form select, +.modal-panel input, +.modal-panel textarea, +.search-box input, +.message-composer textarea { + width: 100%; + border: 1px solid var(--line); + border-radius: 6px; + outline: none; + background: #fff; + color: var(--text); +} + +.login-form input, +.login-form select, +.modal-panel input, +.search-box input { + height: 42px; + padding: 0 12px; +} + +.modal-panel textarea { + min-height: 92px; + resize: vertical; + padding: 10px 12px; + line-height: 1.5; +} + +.login-form .country-code-field input { + height: 40px; + padding: 0 10px 0 0; + border: 0; + border-radius: 0; +} + +.login-form .country-code-field input:focus { + border-color: transparent; + box-shadow: none; +} + +.login-form input:focus, +.login-form select:focus, +.modal-panel input:focus, +.modal-panel textarea:focus, +.search-box input:focus, +.message-composer textarea:focus { + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(23, 125, 114, 0.12); +} + +.login-form input.captcha-input-valid { + border-color: var(--primary); + background: var(--primary); + color: #fff; + font-weight: 700; +} + +.login-form input.captcha-input-valid::-moz-placeholder { + color: rgba(255, 255, 255, 0.75); +} + +.login-form input.captcha-input-valid::placeholder { + color: rgba(255, 255, 255, 0.75); +} + +.login-form input.captcha-input-invalid { + border-color: var(--accent); + color: var(--accent); + box-shadow: 0 0 0 3px rgba(216, 78, 63, 0.12); +} + +.login-form input.field-input-error { + border-color: var(--accent); + color: var(--accent); + box-shadow: 0 0 0 3px rgba(216, 78, 63, 0.12); +} + +.login-form input.field-input-error::-moz-placeholder { + color: rgba(216, 78, 63, 0.78); +} + +.login-form input.field-input-error::placeholder { + color: rgba(216, 78, 63, 0.78); +} + +.captcha-button { + width: 112px; + height: 42px; + padding: 0; + border: 1px solid var(--line); + border-radius: 6px; + background: #f7fbfa; + overflow: hidden; +} + +.captcha-button img { + display: block; + width: 100%; + height: 100%; + -o-object-fit: contain; + object-fit: contain; +} + +.qr-login-card { + position: relative; + width: min(248px, 100%); + aspect-ratio: 1; + display: grid; + place-items: center; + justify-self: center; + border: 1px solid var(--line); + border-radius: 8px; + background: #fff; + overflow: hidden; +} + +.qr-code-button { + width: 100%; + height: 100%; + display: grid; + place-items: center; + padding: 18px; + border: 0; + background: #fff; + color: var(--muted); + font-weight: 700; +} + +.qr-code-button:disabled { + cursor: default; +} + +.qr-code-button img { + width: 100%; + height: 100%; + -o-object-fit: contain; + object-fit: contain; + image-rendering: pixelated; +} + +.qr-login-card.scanned .qr-code-button, +.qr-login-card.expired .qr-code-button, +.qr-login-card.error .qr-code-button { + opacity: 0.22; +} + +.qr-scan-user { + position: absolute; + inset: 0; + display: grid; + place-items: center; + background: rgba(255, 255, 255, 0.76); +} + +.qr-scan-user img, +.qr-scan-user span { + width: 70px; + height: 70px; + display: grid; + place-items: center; + border-radius: 8px; + background: var(--primary); + color: #fff; + font-size: 28px; + font-weight: 800; + -o-object-fit: cover; + object-fit: cover; +} + +.qr-login-status { + min-height: 48px; + display: grid; + gap: 4px; + text-align: center; +} + +.qr-login-status strong { + color: var(--text); + font-size: 15px; +} + +.qr-login-status span { + color: var(--muted); + font-size: 13px; +} + +.primary-action, +.primary-inline-action, +.send-button { + border: 0; + border-radius: 6px; + background: var(--primary); + color: #fff; + font-weight: 700; +} + +.primary-action { + height: 44px; +} + +.primary-action:hover, +.primary-inline-action:hover, +.send-button:hover { + background: var(--primary-strong); +} + +.primary-action:disabled, +.primary-inline-action:disabled, +.send-button:disabled { + cursor: not-allowed; + opacity: 0.55; +} + +.ghost-action { + width: 100%; + height: 40px; + margin-top: 12px; + border: 1px solid var(--line); + border-radius: 6px; + background: #fff; + color: var(--text); +} + +.ghost-inline-action, +.primary-inline-action, +.danger-inline-action { + height: 38px; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0 16px; + border-radius: 6px; + font-weight: 700; + line-height: 1; +} + +.ghost-inline-action { + border: 1px solid var(--line); + background: #fff; + color: var(--text); +} + +.ghost-inline-action:hover { + background: #edf2f5; +} + +.danger-inline-action { + border: 1px solid #ef4444; + background: #fff; + color: #b91c1c; +} + +.danger-inline-action:hover { + background: #fee2e2; +} + +.form-error { + margin: 0; + color: var(--accent); + font-size: 13px; +} + +.form-info { + margin: 0; + color: var(--muted); + font-size: 13px; +} + +.app-shell { + height: 100dvh; + min-height: 0; + display: grid; + grid-template-columns: 64px minmax(260px, 340px) minmax(0, 1fr); + overflow: hidden; + background: var(--panel); +} + +.documented-shell { + min-height: 100dvh; + display: grid; + grid-template-columns: 260px minmax(0, 1fr); + background: + radial-gradient(circle at top left, rgba(23, 125, 114, 0.08), transparent 28%), + linear-gradient(180deg, #f4f8f7 0%, #eef3f7 100%); +} + +.documented-rail { + min-height: 0; + display: grid; + align-content: start; + gap: 14px; + padding: 24px 20px; + border-right: 1px solid var(--line); + background: rgba(255, 255, 255, 0.82); + backdrop-filter: blur(12px); +} + +.documented-avatar { + width: 64px; + height: 64px; + display: grid; + place-items: center; + overflow: hidden; + border-radius: 16px; + background: #dff4ef; + color: var(--primary-strong); + font-size: 28px; + font-weight: 800; +} + +.documented-avatar img { + width: 100%; + height: 100%; + -o-object-fit: cover; + object-fit: cover; +} + +.documented-rail-copy { + display: grid; + gap: 4px; +} + +.documented-rail-copy strong { + font-size: 16px; +} + +.documented-rail-copy small { + color: var(--muted); + overflow-wrap: anywhere; +} + +.documented-rail-button { + min-height: 40px; + padding: 0 14px; + border: 0; + border-radius: 10px; + background: var(--primary); + color: #fff; + font-weight: 700; + text-align: left; +} + +.documented-rail-button.ghost { + border: 1px solid var(--line); + background: #fff; + color: var(--text); +} + +.documented-main { + min-width: 0; + min-height: 0; + overflow: auto; + padding: 28px; + display: grid; + align-content: start; + gap: 18px; +} + +.documented-header { + display: flex; + align-items: end; + justify-content: space-between; + gap: 18px; +} + +.documented-header h1 { + margin: 0; + font-size: 28px; +} + +.documented-header p { + margin: 6px 0 0; + color: var(--muted); +} + +.documented-status { + min-width: 180px; + display: grid; + gap: 4px; + padding: 14px 16px; + border: 1px solid var(--line); + border-radius: 14px; + background: rgba(255, 255, 255, 0.88); +} + +.documented-status span { + color: var(--muted); + font-size: 12px; +} + +.documented-status strong { + font-size: 15px; +} + +.documented-card { + display: grid; + gap: 12px; + padding: 20px; + border: 1px solid var(--line); + border-radius: 16px; + background: rgba(255, 255, 255, 0.88); + box-shadow: 0 16px 36px rgba(24, 33, 47, 0.06); +} + +.documented-card h2 { + margin: 0; + font-size: 18px; +} + +.documented-text { + margin: 0; + color: var(--muted); + line-height: 1.6; +} + +.documented-list { + margin: 0; + padding-left: 18px; + display: grid; + gap: 8px; + line-height: 1.55; +} + +.documented-list.muted { + color: var(--muted); +} + +.documented-meta { + margin: 0; + display: grid; + gap: 10px; +} + +.documented-meta div { + display: grid; + grid-template-columns: 110px minmax(0, 1fr); + gap: 12px; + padding-top: 10px; + border-top: 1px solid var(--line); +} + +.documented-meta dt { + color: var(--muted); +} + +.documented-meta dd { + margin: 0; + overflow-wrap: anywhere; +} + +.documented-password-panel { + width: min(100%, 360px); +} + +.navigation-rail { + min-height: 0; + display: flex; + flex-direction: column; + align-items: center; + gap: 14px; + padding: 16px 10px; + background: var(--rail); +} + +.rail-avatar { + width: 40px; + height: 40px; + border-radius: 8px; + background: #e9f7f4; + color: var(--primary-strong); + font-weight: 800; + overflow: hidden; +} + +.rail-avatar-button { + position: relative; + border: 0; + padding: 0; + cursor: pointer; +} + +.rail-avatar-button::after { + content: "+"; + position: absolute; + right: -2px; + bottom: -2px; + width: 15px; + height: 15px; + display: grid; + place-items: center; + border: 1px solid var(--rail); + border-radius: 50%; + background: var(--primary); + color: #fff; + font-size: 11px; + font-weight: 800; + line-height: 1; +} + +.rail-avatar-button:hover, +.rail-avatar-button:focus-visible { + box-shadow: 0 0 0 3px rgba(47, 196, 178, 0.28); +} + +.rail-avatar img { + width: 100%; + height: 100%; + -o-object-fit: cover; + object-fit: cover; +} + +.rail-button, +.icon-button { + display: grid; + place-items: center; + border: 0; + border-radius: 6px; + background: transparent; +} + +.rail-button { + width: 40px; + height: 40px; + color: #dbe5ea; + font-size: 18px; +} + +.rail-button svg { + width: 22px; + height: 22px; + fill: none; + stroke: currentColor; + stroke-width: 1.8; + stroke-linecap: round; + stroke-linejoin: round; +} + +.rail-button.active, +.rail-button:hover { + background: rgba(255, 255, 255, 0.12); +} + +.rail-logout-button:hover { + background: rgba(239, 68, 68, 0.2); + color: #fecaca; +} + +.rail-spacer { + flex: 1; +} + +.conversation-list { + position: relative; + min-height: 0; + display: grid; + grid-template-rows: 72px auto minmax(0, 1fr); + border-right: 1px solid var(--line); + background: var(--panel-soft); + overflow: hidden; +} + +.modal-backdrop { + position: absolute; + inset: 0; + z-index: 5; + display: grid; + place-items: start center; + padding: 88px 16px 16px; + background: rgba(24, 33, 47, 0.28); +} + +.modal-panel { + width: min(100%, 308px); + display: grid; + gap: 14px; + padding: 16px; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--panel); + box-shadow: 0 18px 40px rgba(24, 33, 47, 0.18); +} + +.modal-panel header, +.modal-actions { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; +} + +.modal-panel header strong { + font-size: 16px; +} + +.modal-panel label { + display: grid; + gap: 8px; + color: var(--muted); + font-size: 13px; +} + +.settings-backdrop { + z-index: 24; + place-items: center; + padding: 24px 16px; +} + +.settings-panel { + width: min(620px, calc(100vw - 32px)); + max-height: calc(100dvh - 48px); + display: grid; + gap: 14px; + overflow: auto; + padding: 16px; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--panel); + box-shadow: 0 18px 40px rgba(24, 33, 47, 0.18); +} + +.settings-panel header, +.settings-password-form footer { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.settings-panel header strong { + font-size: 16px; +} + +.settings-layout { + display: grid; + grid-template-columns: 170px minmax(0, 1fr); + gap: 14px; +} + +.settings-menu { + display: grid; + align-content: start; + gap: 8px; +} + +.settings-menu button { + min-height: 46px; + display: grid; + gap: 2px; + justify-items: start; + padding: 8px 10px; + border: 1px solid var(--line); + border-radius: 6px; + background: #fff; + color: var(--text); + text-align: left; +} + +.settings-menu button.active, +.settings-menu button:hover { + border-color: var(--primary); + background: #e8f4f2; + color: var(--primary-strong); +} + +.settings-menu button span { + font-weight: 700; +} + +.settings-menu button small { + color: var(--muted); + font-size: 11px; +} + +.settings-password-form, +.settings-placeholder { + min-height: 250px; + display: grid; + align-content: start; + gap: 12px; +} + +.settings-password-form h3, +.settings-placeholder h3 { + margin: 0; + font-size: 16px; +} + +.settings-password-form label { + display: grid; + gap: 8px; + color: var(--muted); + font-size: 13px; +} + +.settings-password-form input { + width: 100%; + height: 42px; + padding: 0 12px; + border: 1px solid var(--line); + border-radius: 6px; + outline: none; + background: #fff; + color: var(--text); +} + +.settings-password-form input:focus { + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(23, 125, 114, 0.12); +} + +.settings-password-form footer { + margin-top: 4px; + justify-content: flex-end; + flex-wrap: wrap; +} + +.settings-password-form footer .inline-save-status { + margin-right: auto; +} + +.settings-placeholder { + place-items: center; + border: 1px dashed var(--line); + border-radius: 8px; + color: var(--muted); + text-align: center; +} + +.settings-placeholder p { + margin: 0; +} + +.profile-edit-panel { + width: min(100%, 360px); +} + +.profile-avatar-field { + display: grid; + justify-items: center; + gap: 10px; +} + +.profile-avatar-field input[type="file"] { + position: absolute; + inline-size: 1px; + block-size: 1px; + opacity: 0; + pointer-events: none; +} + +.profile-avatar-preview { + width: 88px; + height: 88px; + display: grid; + place-items: center; + overflow: hidden; + padding: 0; + border: 0; + border-radius: 8px; + background: #dce8f2; + color: #24435c; + cursor: default; + font-size: 30px; + font-weight: 800; +} + +.profile-avatar-preview.clickable { + cursor: zoom-in; +} + +.profile-avatar-preview.clickable:hover { + box-shadow: 0 0 0 3px rgba(23, 125, 114, 0.16); +} + +.profile-avatar-preview img { + width: 100%; + height: 100%; + -o-object-fit: cover; + object-fit: cover; +} + +.profile-avatar-change-button { + min-width: 96px; +} + +.avatar-preview-backdrop { + z-index: 32; + place-items: center; + padding: 24px 16px; +} + +.avatar-preview-dialog { + width: min(520px, calc(100vw - 32px)); + max-height: calc(100dvh - 48px); + display: grid; + gap: 12px; + padding: 14px; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--panel); + box-shadow: 0 18px 40px rgba(24, 33, 47, 0.18); +} + +.avatar-preview-dialog header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.avatar-preview-actions { + display: flex; + align-items: center; + gap: 6px; +} + +.avatar-preview-actions button { + min-width: 34px; + height: 34px; + display: inline-flex; + align-items: center; + justify-content: center; + border: 1px solid var(--line); + border-radius: 6px; + background: #fff; + color: var(--text); + font-weight: 700; +} + +.avatar-preview-actions .icon-button { + border: 0; + color: var(--muted); +} + +.avatar-preview-stage { + height: min(430px, calc(100dvh - 160px)); + min-height: 260px; + display: grid; + place-items: center; + overflow: hidden; + border-radius: 8px; + background: #101820; + cursor: zoom-in; + touch-action: none; +} + +.avatar-preview-stage.dragging { + cursor: grabbing; +} + +.avatar-preview-stage img { + max-width: min(100%, 430px); + max-height: 100%; + border-radius: 8px; + -o-object-fit: contain; + object-fit: contain; + transform-origin: center center; + transition: transform 0.08s ease-out; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + pointer-events: none; +} + +.avatar-preview-stage.dragging img { + transition: none; +} + +.avatar-cropper-backdrop { + z-index: 30; + place-items: center; + padding: 24px 16px; +} + +.avatar-cropper-panel { + width: min(720px, calc(100vw - 32px)); + max-height: calc(100dvh - 48px); + overflow: auto; +} + +.avatar-cropper-header-actions { + display: flex; + align-items: center; + gap: 8px; +} + +.avatar-cropper-layout { + display: grid; + grid-template-columns: minmax(0, 1fr) 190px; + gap: 14px; +} + +.avatar-cropper-stage { + position: relative; + height: 360px; + min-height: 360px; + overflow: hidden; + border: 1px dashed #b8ccd7; + border-radius: 8px; + background: #0f1b24; + touch-action: none; + cursor: grab; +} + +.avatar-cropper-stage.dragging { + cursor: grabbing; +} + +.avatar-cropper-stage canvas { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + display: block; +} + +.avatar-cropper-box { + position: absolute; + left: 50%; + top: 50%; + width: 74%; + aspect-ratio: 1; + max-width: calc(100% - 44px); + max-height: calc(100% - 44px); + transform: translate(-50%, -50%); + border: 2px dashed var(--primary); + border-radius: 8px; + box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.72); + pointer-events: none; +} + +.avatar-cropper-mask { + position: absolute; + inset: 0; + pointer-events: none; +} + +.avatar-cropper-placeholder { + position: absolute; + inset: 0; + display: grid; + place-items: center; + color: #d9e8ee; + font-size: 13px; + pointer-events: none; +} + +.avatar-cropper-controls { + min-width: 0; + display: grid; + align-content: start; + gap: 12px; +} + +.avatar-cropper-tools { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 8px; +} + +.avatar-cropper-tools button { + min-height: 34px; + padding: 0 10px; + border: 1px solid var(--line); + border-radius: 6px; + background: #fff; + color: var(--text); + font-weight: 700; +} + +.avatar-cropper-tools button:hover { + border-color: var(--primary); + color: var(--primary-strong); +} + +.avatar-cropper-preview { + width: 108px; + height: 108px; + justify-self: center; + overflow: hidden; + border: 1px solid var(--line); + border-radius: 8px; + background: #eef4f7; +} + +.avatar-cropper-preview canvas { + width: 100%; + height: 100%; + display: block; +} + +.avatar-cropper-controls footer { + display: flex; + align-items: center; + justify-content: flex-end; + flex-wrap: wrap; + gap: 8px; +} + +.avatar-cropper-controls footer .inline-save-status { + flex: 1 1 100%; +} + +.modal-actions { + justify-content: flex-end; +} + +.list-header, +.chat-header { + display: flex; + align-items: center; + justify-content: space-between; + min-height: 72px; + padding: 16px 18px; + border-bottom: 1px solid var(--line); +} + +.icon-button { + width: 34px; + height: 34px; + color: var(--muted); + font-size: 20px; +} + +.icon-button:hover { + background: #edf2f5; + color: var(--text); +} + +.search-box { + padding: 12px 14px; +} + +.sidebar-scroll { + min-height: 0; + overflow: auto; + overscroll-behavior: contain; +} + +.conversation-item { + width: 100%; + display: grid; + grid-template-columns: 42px minmax(0, 1fr) auto; + align-items: center; + gap: 10px; + padding: 12px 14px; + border: 0; + border-top: 1px solid rgba(217, 224, 231, 0.7); + background: transparent; + color: inherit; + text-align: left; +} + +.conversation-item.active, +.conversation-item:hover { + background: #eaf3f1; +} + +.favorite-item { + width: 100%; + display: grid; + grid-template-columns: 32px minmax(0, 1fr) auto; + align-items: center; + gap: 10px; + padding: 12px 14px; + border: 0; + border-top: 1px solid rgba(217, 224, 231, 0.7); + background: transparent; + color: inherit; + text-align: left; +} + +.favorite-item.active, +.favorite-item:hover { + background: #eaf3f1; +} + +.favorite-item-icon { + width: 32px; + height: 32px; + display: grid; + place-items: center; + border-radius: 6px; + background: #fff7df; + color: #c79520; + font-size: 16px; +} + +.favorite-item-main { + min-width: 0; + display: grid; + gap: 4px; +} + +.favorite-item-main strong, +.favorite-item-main small { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.favorite-item-main strong { + font-size: 14px; +} + +.favorite-item-main small, +.favorite-item time { + color: var(--muted); + font-size: 12px; +} + +.favorite-panel { + min-width: 0; + min-height: 0; + display: grid; + grid-template-rows: auto minmax(0, 1fr); + background: #eef3f6; +} + +.favorite-detail { + min-height: 0; + display: grid; + align-content: start; + gap: 16px; + padding: 28px; + overflow: auto; +} + +.favorite-detail-message { + width: min(680px, 100%); + display: grid; + grid-template-columns: auto minmax(0, 1fr); + gap: 10px 12px; + padding: 18px; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--panel); + box-shadow: 0 8px 24px rgba(24, 33, 47, 0.08); +} + +.favorite-detail-message.recalled { + border-color: #f1c7c1; + background: #fff7f6; +} + +.favorite-detail-star { + color: #c79520; + font-size: 18px; + line-height: 1.4; +} + +.favorite-detail-body { + min-width: 0; + display: grid; + gap: 10px; +} + +.favorite-detail-message p { + margin: 0; + white-space: pre-wrap; + overflow-wrap: anywhere; + line-height: 1.55; +} + +.favorite-detail-message time { + grid-column: 2; + color: var(--muted); + font-size: 12px; +} + +.favorite-detail-image { + width: min(420px, 100%); + display: block; + overflow: hidden; + border: 1px solid #b9ded8; + border-radius: 8px; + background: #fff; +} + +.favorite-detail-image img { + display: block; + width: 100%; + max-height: 360px; + -o-object-fit: contain; + object-fit: contain; + background: #f8fbfc; +} + +.favorite-detail-file { + width: min(360px, 100%); +} + +.favorite-detail-actions { + width: min(680px, 100%); + display: flex; + align-items: center; + justify-content: flex-end; + gap: 8px; +} + +.favorite-detail-actions .inline-save-status { + margin-right: auto; +} + +.empty-list, +.empty-chat { + display: grid; + place-items: center; + align-content: center; + gap: 8px; + color: var(--muted); + text-align: center; +} + +.empty-list { + min-height: 180px; + padding: 24px; +} + +.empty-chat { + min-height: 0; + padding: 24px; +} + +.empty-list strong, +.empty-chat strong { + color: var(--text); +} + +.conversation-avatar { + width: 42px; + height: 42px; + border-radius: 8px; + background: #dce8f2; + color: #24435c; + font-weight: 700; +} + +.conversation-copy { + min-width: 0; + display: grid; + gap: 4px; +} + +.conversation-copy strong, +.conversation-copy small { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.conversation-copy small { + color: var(--muted); +} + +.unread-badge { + justify-self: end; + min-width: 20px; + height: 20px; + padding: 0 6px; + border-radius: 999px; + background: #ef4444; + color: #fff; + font-size: 12px; + font-weight: 800; + line-height: 20px; + text-align: center; + box-shadow: 0 0 0 2px #f7fafc; +} + +.chat-panel { + position: relative; + min-width: 0; + min-height: 0; + height: 100%; + display: grid; + grid-template-rows: 72px minmax(0, 1fr) auto auto; + overflow: hidden; + background: #eef2f5; +} + +.contact-panel { + min-width: 0; + min-height: 0; + height: 100%; + display: grid; + grid-template-rows: 72px minmax(0, 1fr); + overflow: auto; + background: #eef2f5; +} + +.contact-profile { + align-self: start; + width: min(520px, calc(100% - 48px)); + margin: 48px auto; + display: grid; + justify-items: center; + gap: 18px; + padding: 32px; + background: var(--panel); + border: 1px solid var(--line); + border-radius: 8px; +} + +.profile-avatar { + width: 76px; + height: 76px; + display: grid; + place-items: center; + border-radius: 8px; + background: #dce8f2; + color: #24435c; + font-size: 28px; + font-weight: 800; +} + +.profile-main { + text-align: center; +} + +.remark-row { + min-width: 0; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; +} + +.profile-main h3 { + margin: 0; + font-size: 22px; + overflow-wrap: anywhere; +} + +.remark-edit-button { + width: 32px; + height: 32px; + flex: 0 0 auto; + font-size: 16px; +} + +.profile-main p { + margin: 6px 0 0; + color: var(--muted); +} + +.profile-fields { + width: 100%; + display: grid; + gap: 12px; + margin: 0; +} + +.profile-fields div { + display: grid; + grid-template-columns: 80px minmax(0, 1fr); + gap: 12px; + padding: 10px 0; + border-top: 1px solid var(--line); +} + +.profile-fields dt { + color: var(--muted); +} + +.profile-fields dd { + margin: 0; + overflow-wrap: anywhere; +} + +.profile-action { + width: 160px; +} + +.contact-panel .contact-modal-backdrop { + position: fixed; +} + +.header-actions { + display: flex; + gap: 8px; +} + +.chat-header-actions { + position: relative; +} + +.chat-title-button { + display: grid; + grid-template-columns: 38px minmax(0, 1fr); + align-items: center; + gap: 10px; + max-width: min(460px, 52vw); + padding: 0; + border: 0; + background: transparent; + color: var(--text); + text-align: left; +} + +.chat-title-avatar { + width: 38px; + height: 38px; + display: grid; + place-items: center; + border-radius: 8px; + background: #dce8f2; + color: #24435c; + font-size: 16px; + font-weight: 800; +} + +.chat-title-copy { + min-width: 0; +} + +.chat-title-button h2 { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.chat-title-button:hover h2 { + color: var(--primary-strong); +} + +.chat-more-menu { + top: 38px; + width: 150px; +} + +.action-menu-anchor { + position: relative; +} + +.quick-action-menu { + position: absolute; + top: 40px; + right: 0; + z-index: 7; + width: 168px; + display: grid; + padding: 6px; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--panel); + box-shadow: 0 14px 34px rgba(24, 33, 47, 0.16); +} + +.quick-action-menu button { + height: 34px; + padding: 0 10px; + border: 0; + border-radius: 6px; + background: transparent; + color: var(--text); + text-align: left; +} + +.quick-action-menu button:hover { + background: #edf2f5; +} + +.quick-action-menu.chat-more-menu { + top: 38px; + width: 150px; +} + +.conversation-action-panel { + width: min(100%, 340px); + max-height: calc(100dvh - 116px); + overflow: auto; +} + +.contact-picker { + display: grid; + gap: 10px; +} + +.contact-picker > input { + width: 100%; + height: 38px; + padding: 0 10px; + border: 1px solid var(--line); + border-radius: 6px; + outline: none; +} + +.contact-picker-list { + max-height: 220px; + overflow: auto; + display: grid; + gap: 6px; + padding-right: 2px; +} + +.contact-picker-row { + width: 100%; + min-height: 48px; + display: grid; + grid-template-columns: 22px 36px minmax(0, 1fr); + align-items: center; + gap: 8px; + padding: 6px; + border: 1px solid var(--line); + border-radius: 6px; + background: #fff; + color: var(--text); + text-align: left; +} + +.contact-picker-row:hover, +.contact-picker-row.selected { + border-color: #9fd4cc; + background: #edf8f5; +} + +.contact-picker-row .conversation-avatar { + width: 36px; + height: 36px; + border-radius: 6px; + font-size: 13px; +} + +.contact-check { + width: 20px; + height: 20px; + display: grid; + place-items: center; + border: 1px solid var(--line); + border-radius: 4px; + color: var(--primary-strong); + font-size: 13px; + font-weight: 800; +} + +.contact-picker-copy { + min-width: 0; + display: grid; + gap: 2px; +} + +.contact-picker-copy strong, +.contact-picker-copy small { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.contact-picker-copy small { + color: var(--muted); + font-size: 12px; +} + +.contact-picker-empty { + min-height: 64px; + display: grid; + place-items: center; + color: var(--muted); + border: 1px dashed var(--line); + border-radius: 6px; +} + +.group-dialog { + width: min(420px, calc(100% - 32px)); + max-height: calc(100dvh - 116px); + display: grid; + gap: 16px; + padding: 18px; + overflow: auto; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--panel); + box-shadow: 0 18px 40px rgba(24, 33, 47, 0.18); +} + +.group-dialog header, +.group-dialog footer { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.group-dialog h3 { + margin: 0; + font-size: 18px; +} + +.single-profile-dialog { + width: min(420px, calc(100% - 32px)); +} + +.single-profile-summary { + display: grid; + justify-items: center; + gap: 10px; +} + +.single-profile-summary strong { + max-width: 100%; + overflow-wrap: anywhere; + font-size: 18px; +} + +.single-profile-footer { + align-items: center; + justify-content: flex-end; +} + +.single-profile-footer .inline-save-status { + margin-right: auto; +} + +.message-detail-dialog { + width: min(560px, calc(100% - 32px)); +} + +.message-detail-meta div { + grid-template-columns: 88px minmax(0, 1fr); +} + +.message-detail-muted { + color: var(--muted); +} + +.message-detail-summary { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.message-detail-summary span { + min-height: 30px; + display: inline-flex; + align-items: center; + gap: 4px; + padding: 0 10px; + border: 1px solid var(--line); + border-radius: 6px; + background: #f8fbfb; + color: var(--muted); + font-size: 13px; +} + +.message-detail-summary strong { + color: var(--text); +} + +.message-detail-groups { + display: grid; + gap: 14px; +} + +.message-detail-section { + display: grid; + gap: 8px; +} + +.message-detail-section header { + min-height: 28px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + border-bottom: 1px solid var(--line); +} + +.message-detail-section header strong { + font-size: 14px; +} + +.message-detail-section header span { + color: var(--muted); + font-size: 12px; +} + +.message-detail-member-list { + display: grid; + overflow: hidden; + border: 1px solid var(--line); + border-radius: 6px; + background: #fff; +} + +.message-detail-member-row { + display: grid; + grid-template-columns: 34px minmax(0, 1fr) auto auto; + align-items: center; + gap: 8px; + min-height: 48px; + padding: 7px 8px; + border-top: 1px solid var(--line); +} + +.message-detail-member-row:first-child { + border-top: 0; +} + +.message-detail-member-row .conversation-avatar { + width: 34px; + height: 34px; + border-radius: 6px; + font-size: 13px; +} + +.message-detail-member-main { + min-width: 0; + display: grid; + gap: 2px; +} + +.message-detail-member-main strong, +.message-detail-member-main small { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.message-detail-member-main strong { + font-size: 13px; +} + +.message-detail-member-main small, +.message-detail-member-time { + color: var(--muted); + font-size: 12px; +} + +.message-detail-member-time { + white-space: nowrap; +} + +.message-detail-role { + min-height: 22px; + display: inline-flex; + align-items: center; + padding: 0 7px; + border: 1px solid #c7d9e5; + border-radius: 6px; + color: #416272; + font-size: 12px; + white-space: nowrap; +} + +.group-dialog label { + display: grid; + gap: 8px; + color: var(--muted); + font-size: 13px; +} + +.group-dialog input { + width: 100%; + height: 42px; + padding: 0 12px; + border: 1px solid var(--line); + border-radius: 6px; + outline: none; +} + +.group-dialog textarea, +.group-dialog select { + width: 100%; + border: 1px solid var(--line); + border-radius: 6px; + outline: none; + background: #fff; + color: var(--text); +} + +.group-dialog textarea { + min-height: 86px; + resize: vertical; + padding: 10px 12px; + line-height: 1.5; +} + +.group-dialog select { + height: 42px; + padding: 0 10px; +} + +.group-dialog input:focus { + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(23, 125, 114, 0.12); +} + +.group-dialog textarea:focus, +.group-dialog select:focus { + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(23, 125, 114, 0.12); +} + +.group-profile { + display: grid; + gap: 10px; + margin: 0; +} + +.group-profile div { + display: grid; + grid-template-columns: 78px minmax(0, 1fr); + gap: 12px; + padding: 10px 0; + border-top: 1px solid var(--line); +} + +.group-profile dt { + color: var(--muted); +} + +.group-profile dd { + margin: 0; + overflow-wrap: anywhere; +} + +.group-loading { + min-height: 96px; + display: grid; + place-items: center; + color: var(--muted); +} + +.group-member-list { + display: grid; + gap: 8px; +} + +.group-member-row { + display: grid; + grid-template-columns: 42px minmax(0, 1fr); + align-items: center; + gap: 10px; + min-height: 54px; + padding: 8px; + border: 1px solid var(--line); + border-radius: 6px; + background: #fff; +} + +.group-member-row.selectable { + cursor: pointer; +} + +.group-member-row.with-check { + grid-template-columns: 24px 42px minmax(0, 1fr); +} + +.group-member-row.selectable:hover, +.group-member-row.selectable.selected { + border-color: #9fd4cc; + background: #edf8f5; +} + +.group-member-row input[type="checkbox"] { + justify-self: center; + width: 16px; + height: 16px; +} + +.group-member-row span:last-child { + min-width: 0; + display: grid; + gap: 3px; +} + +.group-member-row strong, +.group-member-row small { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.group-member-row small { + color: var(--muted); + font-size: 12px; +} + +.group-management-dialog { + width: min(520px, calc(100% - 32px)); +} + +.group-management-tabs { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.group-management-tabs button { + height: 34px; + padding: 0 10px; + border: 1px solid var(--line); + border-radius: 6px; + background: #fff; + color: var(--text); +} + +.group-management-tabs button.active, +.group-management-tabs button:hover { + border-color: var(--primary); + background: #e8f4f2; + color: var(--primary-strong); + font-weight: 700; +} + +.group-manage-section { + display: grid; + gap: 14px; +} + +.group-manage-section footer { + justify-content: flex-end; +} + +.mute-save-footer { + align-items: center; + gap: 10px; +} + +.inline-save-status { + color: var(--primary-strong); + font-size: 13px; +} + +.inline-save-status.error { + color: var(--accent); +} + +.mute-time-grid { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); + gap: 10px; +} + +.message-list { + min-height: 0; + overflow: auto; + overscroll-behavior: contain; + padding: 24px; +} + +.message-row { + display: flex; + align-items: flex-end; + gap: 8px; + margin-bottom: 14px; +} + +.message-row.out { + justify-content: flex-end; +} + +.message-sender-avatar { + width: 34px; + height: 34px; + flex: 0 0 auto; + display: grid; + place-items: center; + overflow: hidden; + padding: 0; + border: 0; + border-radius: 8px; + background: #dce8f2; + color: #24435c; + font-size: 14px; + font-weight: 800; +} + +.message-sender-avatar img { + width: 100%; + height: 100%; + -o-object-fit: cover; + object-fit: cover; +} + +.message-stack { + max-width: min(68%, 720px); + display: grid; + justify-items: start; + gap: 4px; +} + +.message-row.out .message-stack { + justify-items: end; +} + +.message-sender-name { + min-height: 18px; + max-width: 100%; + padding: 0; + border: 0; + background: transparent; + color: var(--muted); + font-size: 12px; + font-weight: 700; + text-align: left; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.message-sender-name:hover { + color: var(--primary-strong); +} + +.message-sender-role { + margin-left: 4px; + color: var(--primary-strong); + font-size: 11px; +} + +.message-row.selected .message-bubble { + box-shadow: 0 0 0 2px rgba(24, 138, 121, 0.26); +} + +.message-row.attention .message-bubble { + animation: message-attention 1.4s ease-in-out 2; + box-shadow: 0 0 0 3px rgba(24, 138, 121, 0.3); +} + +@keyframes message-attention { + 0%, + 100% { + box-shadow: 0 0 0 2px rgba(24, 138, 121, 0.16); + } + + 45% { + box-shadow: 0 0 0 5px rgba(24, 138, 121, 0.36); + } +} + +.message-select { + width: 24px; + height: 24px; + display: grid; + place-items: center; +} + +.message-select input { + width: 16px; + height: 16px; +} + +.message-bubble { + position: relative; + max-width: min(68%, 720px); + padding: 10px 12px 8px; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--bubble-in); + box-shadow: 0 2px 8px rgba(30, 40, 50, 0.05); +} + +.message-stack .message-bubble { + max-width: 100%; +} + +.message-row.out .message-bubble { + background: var(--bubble-out); + border-color: #bfe4dc; +} + +.message-bubble:hover { + border-color: #9fd4cc; +} + +.message-bubble.role-bubble-owner, +.message-bubble.role-bubble-admin { + overflow: hidden; +} + +.message-bubble.role-bubble-owner::after, +.message-bubble.role-bubble-admin::after { + content: ""; + position: absolute; + top: 0; + right: 0; + width: 0; + height: 0; + border-top: 16px solid #d79d22; + border-left: 16px solid transparent; +} + +.message-bubble.role-bubble-admin::after { + border-top-color: #178a79; +} + +.message-bubble p { + margin: 0; + line-height: 1.5; + white-space: pre-wrap; + overflow-wrap: anywhere; +} + +.message-image { + padding: 0; + border: 0; + display: block; + max-width: min(280px, 62vw); + overflow: hidden; + border-radius: 6px; + background: #edf2f5; + cursor: zoom-in; +} + +.message-image + p, +.message-file + p { + margin-top: 8px; +} + +.message-image img { + display: block; + max-width: 100%; + max-height: 320px; + -o-object-fit: contain; + object-fit: contain; +} + +.image-viewer-backdrop { + position: fixed; + inset: 0; + z-index: 80; + display: grid; + place-items: center; + padding: 24px; + background: rgba(12, 18, 26, 0.58); +} + +.image-viewer-dialog { + width: min(880px, calc(100vw - 48px)); + height: min(640px, calc(100vh - 48px)); + min-width: 360px; + min-height: 280px; + max-width: calc(100vw - 48px); + max-height: calc(100vh - 48px); + display: grid; + grid-template-rows: 52px minmax(0, 1fr); + overflow: hidden; + resize: both; + border: 1px solid rgba(255, 255, 255, 0.18); + border-radius: 8px; + background: #101820; + box-shadow: 0 24px 70px rgba(0, 0, 0, 0.36); +} + +.image-viewer-toolbar { + min-width: 0; + display: flex; + align-items: center; + justify-content: flex-end; + gap: 8px; + padding: 8px 10px; + border-bottom: 1px solid rgba(255, 255, 255, 0.12); + background: rgba(24, 33, 47, 0.94); +} + +.image-viewer-title { + min-width: 0; + flex: 1; + overflow: hidden; + color: #f8fafc; + font-size: 13px; + font-weight: 700; + text-overflow: ellipsis; + white-space: nowrap; +} + +.image-viewer-toolbar button { + width: 36px; + height: 36px; + display: grid; + place-items: center; + border: 0; + border-radius: 6px; + background: transparent; + color: #f8fafc; + font-size: 17px; + font-weight: 700; + line-height: 1; +} + +.image-viewer-toolbar button:hover { + background: rgba(255, 255, 255, 0.14); +} + +.image-viewer-stage { + min-width: 0; + min-height: 0; + display: grid; + place-items: center; + overflow: hidden; + background: #0c121a; + cursor: default; + touch-action: none; +} + +.image-viewer-stage.zoomed { + cursor: grab; +} + +.image-viewer-stage.dragging { + cursor: grabbing; +} + +.image-viewer-img { + max-width: calc(100% - 32px); + max-height: calc(100% - 32px); + -o-object-fit: contain; + object-fit: contain; + transform-origin: center center; + transition: transform 0.08s ease-out; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + pointer-events: none; +} + +.image-viewer-stage.dragging .image-viewer-img { + transition: none; +} + +.sender-profile-backdrop { + position: fixed; + inset: 0; + z-index: 84; + display: grid; + place-items: center; + padding: 24px; + background: rgba(24, 33, 47, 0.32); +} + +.sender-profile-dialog { + width: min(380px, calc(100vw - 32px)); + display: grid; + gap: 16px; + padding: 18px; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--panel); + box-shadow: 0 18px 40px rgba(24, 33, 47, 0.18); +} + +.sender-profile-dialog header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.sender-profile-main { + display: grid; + justify-items: center; + gap: 8px; + text-align: center; +} + +.sender-profile-main strong { + max-width: 100%; + overflow-wrap: anywhere; + font-size: 18px; +} + +.sender-profile-main span { + color: var(--primary-strong); + font-size: 12px; + font-weight: 700; +} + +.sender-profile-fields { + display: grid; + gap: 8px; + margin: 0; +} + +.sender-profile-fields div { + display: grid; + grid-template-columns: 72px minmax(0, 1fr); + gap: 10px; + padding-top: 8px; + border-top: 1px solid var(--line); +} + +.sender-profile-fields dt { + color: var(--muted); +} + +.sender-profile-fields dd { + margin: 0; + overflow-wrap: anywhere; +} + +.sender-profile-alias { + display: grid; + gap: 8px; + color: var(--muted); + font-size: 13px; +} + +.sender-profile-alias input { + width: 100%; + height: 40px; + padding: 0 12px; + border: 1px solid var(--line); + border-radius: 6px; + outline: none; + background: #fff; + color: var(--text); +} + +.sender-profile-alias input:focus { + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(23, 125, 114, 0.12); +} + +.sender-profile-actions { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 8px; +} + +.sender-profile-actions .inline-save-status { + margin-right: auto; +} + +@media (max-width: 480px) { + .image-viewer-backdrop { + padding: 12px; + } + + .image-viewer-dialog { + width: calc(100vw - 24px); + height: min(560px, calc(100vh - 24px)); + min-width: 280px; + min-height: 240px; + max-width: calc(100vw - 24px); + max-height: calc(100vh - 24px); + } + + .image-viewer-toolbar { + gap: 4px; + padding: 6px; + } + + .image-viewer-toolbar button { + width: 32px; + height: 32px; + font-size: 15px; + } + + .message-detail-member-row { + grid-template-columns: 34px minmax(0, 1fr); + } + + .message-detail-member-time, + .message-detail-role { + grid-column: 2; + justify-self: start; + } + + .avatar-cropper-panel { + width: calc(100vw - 24px); + max-height: calc(100dvh - 24px); + } + + .avatar-cropper-layout { + grid-template-columns: minmax(0, 1fr); + } + + .avatar-cropper-stage { + height: 300px; + min-height: 300px; + } + + .avatar-cropper-controls { + grid-template-columns: minmax(0, 1fr) auto; + align-items: center; + } + + .avatar-cropper-tools { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + .avatar-cropper-preview { + width: 82px; + height: 82px; + } + + .avatar-cropper-controls footer { + grid-column: 1 / -1; + } + + .settings-panel { + width: calc(100vw - 24px); + max-height: calc(100dvh - 24px); + } + + .settings-layout { + grid-template-columns: minmax(0, 1fr); + } + + .settings-menu { + grid-auto-flow: column; + grid-auto-columns: minmax(120px, 1fr); + overflow-x: auto; + padding-bottom: 2px; + } + + .settings-password-form footer .inline-save-status { + flex: 1 1 100%; + } +} + +.message-file { + width: min(280px, 62vw); + display: grid; + grid-template-columns: 38px minmax(0, 1fr); + align-items: center; + gap: 10px; + padding: 10px; + border: 1px solid #d6e2e7; + border-radius: 6px; + background: #f8fbfc; + color: var(--text); + text-decoration: none; +} + +.message-file:hover { + border-color: #9fd4cc; + background: #edf8f5; +} + +.message-file-icon { + width: 38px; + height: 38px; + display: grid; + place-items: center; + border-radius: 6px; + background: #e3eff2; + color: var(--primary-strong); + font-size: 18px; + font-weight: 800; +} + +.message-file-main { + min-width: 0; + display: grid; + gap: 3px; +} + +.message-file-main strong, +.message-file-main small { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.message-file-main strong { + font-size: 13px; +} + +.message-file-main small { + color: var(--muted); + font-size: 11px; +} + +.message-bubble time { + display: flex; + align-items: center; + justify-content: flex-end; + flex-wrap: wrap; + gap: 4px; + margin-top: 4px; + color: var(--muted); + font-size: 11px; + text-align: right; +} + +.recall-mark { + margin-right: auto; + color: #d93025; + font-size: 11px; + font-weight: 700; + line-height: 1; +} + +.edit-mark { + color: #7a8792; + font-size: 11px; + font-weight: 600; + line-height: 1; +} + +.delivery-mark { + display: inline-flex; + align-items: center; + justify-content: flex-end; + min-width: 14px; + color: #8f9aa3; + font-size: 14px; + font-weight: 800; + letter-spacing: 0; + line-height: 1; +} + +.delivery-mark-delivered, +.delivery-mark-read { + min-width: 19px; +} + +.delivery-mark-read { + color: #1aa66a; +} + +.favorite-mark { + color: #c79520; + font-size: 12px; + line-height: 1; +} + +.delivery-check { + display: inline-block; +} + +.delivery-check-second { + margin-left: -5px; +} + +.delivery-error { + color: #c0392b; +} + +.message-context-menu { + position: fixed; + z-index: 30; + width: 136px; + padding: 6px; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--panel); + box-shadow: 0 16px 40px rgba(24, 33, 47, 0.18); +} + +.message-context-menu button { + width: 100%; + min-height: 32px; + padding: 0 10px; + border: 0; + border-radius: 6px; + background: transparent; + color: var(--text); + text-align: left; +} + +.message-context-menu button:hover { + background: #eaf3f1; +} + +.selection-toolbar { + min-height: 42px; + display: flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + border-top: 1px solid var(--line); + background: #f7fafc; +} + +.selection-toolbar span { + flex: 1; + color: var(--muted); + font-size: 13px; +} + +.selection-toolbar button { + min-height: 30px; + padding: 0 10px; + border-radius: 6px; +} + +.forward-dialog, +.edit-dialog { + width: min(100%, 340px); + max-height: min(520px, calc(100vh - 130px)); + display: grid; + gap: 4px; + padding: 14px; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--panel); + box-shadow: 0 18px 40px rgba(24, 33, 47, 0.18); + overflow: auto; +} + +.forward-dialog header, +.edit-dialog header, +.forward-target { + display: flex; + align-items: center; + gap: 10px; +} + +.forward-dialog header, +.edit-dialog header { + justify-content: space-between; + margin-bottom: 6px; +} + +.forward-dialog h3, +.edit-dialog h3 { + margin: 0; + font-size: 16px; +} + +.edit-dialog { + width: min(100%, 420px); + gap: 12px; +} + +.edit-dialog textarea { + width: 100%; + min-height: 132px; + resize: vertical; + border-radius: 8px; + border: 1px solid var(--line); + padding: 10px; + background: #f9fbfc; + color: var(--text); + line-height: 1.5; +} + +.edit-dialog textarea:focus { + border-color: var(--accent); + outline: 0; + box-shadow: 0 0 0 3px rgba(24, 138, 121, 0.14); +} + +.edit-dialog footer { + display: flex; + justify-content: flex-end; + gap: 8px; +} + +.edit-dialog footer button { + min-height: 34px; + padding: 0 14px; + border-radius: 8px; +} + +.forward-target { + min-height: 52px; + padding: 6px; + border: 0; + border-radius: 8px; + background: transparent; + color: var(--text); + text-align: left; +} + +.forward-target:hover { + background: #eaf3f1; +} + +.chat-toast { + position: absolute; + left: 50%; + bottom: 132px; + z-index: 20; + transform: translateX(-50%); + padding: 8px 12px; + border-radius: 8px; + background: rgba(35, 45, 58, 0.92); + color: #fff; + font-size: 13px; + pointer-events: none; +} + +.chat-toast.error { + background: rgba(192, 57, 43, 0.94); +} + +.message-composer { + position: relative; + min-height: 0; + border-top: 1px solid var(--line); + background: var(--panel); +} + +.message-composer::after { + content: ""; + position: absolute; + inset: 6px; + z-index: 4; + border: 2px dashed transparent; + border-radius: 8px; + pointer-events: none; + transition: border-color 0.12s ease, background-color 0.12s ease; +} + +.message-composer.drop-active::after { + border-color: var(--primary); + background: rgba(23, 125, 114, 0.08); +} + +.quote-preview { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 16px 0; + color: var(--muted); + font-size: 13px; +} + +.quote-preview span { + min-width: 0; + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + border-left: 3px solid var(--accent); + padding-left: 8px; +} + +.quote-preview strong { + margin-right: 6px; + color: var(--text); +} + +.quote-preview button { + width: 26px; + height: 26px; + border: 0; + border-radius: 6px; + background: transparent; + color: var(--muted); + font-size: 18px; +} + +.quote-preview button:hover { + background: #edf2f5; +} + +.composer-tools { + position: relative; + display: flex; + gap: 4px; + padding: 8px 16px 0; +} + +.composer-tools input[type="file"] { + display: none; +} + +.composer-tools button { + width: 32px; + height: 32px; + border: 0; + border-radius: 6px; + background: transparent; + color: var(--muted); + font-size: 18px; +} + +.composer-tools button:hover { + background: #edf2f5; +} + +.emoji-picker { + position: absolute; + left: 16px; + bottom: 38px; + z-index: 12; + width: 256px; + display: grid; + grid-template-columns: repeat(8, 1fr); + gap: 2px; + padding: 8px; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--panel); + box-shadow: 0 14px 34px rgba(24, 33, 47, 0.16); +} + +.emoji-picker button { + width: 28px; + height: 28px; + border-radius: 6px; + font-size: 17px; +} + +.message-composer textarea { + display: block; + min-height: 86px; + max-height: 180px; + padding: 12px 16px; + border: 0; + border-radius: 0; + resize: none; + box-shadow: none; +} + +.composer-footer { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 0 16px 14px; + color: var(--muted); + font-size: 12px; +} + +.send-button { + min-width: 72px; + height: 34px; +} + +@media (max-width: 760px) { + .app-shell { + grid-template-columns: 56px minmax(0, 1fr); + } + + .conversation-list { + display: none; + } + + .message-bubble { + max-width: 86%; + } +} + diff --git a/dist/favicon.ico b/dist/favicon.ico new file mode 100644 index 0000000..a758e41 Binary files /dev/null and b/dist/favicon.ico differ diff --git a/dist/img/logo.png b/dist/img/logo.png new file mode 100644 index 0000000..1ad08f9 Binary files /dev/null and b/dist/img/logo.png differ diff --git a/dist/index.html b/dist/index.html new file mode 100644 index 0000000..2380868 --- /dev/null +++ b/dist/index.html @@ -0,0 +1 @@ +Web IM
\ No newline at end of file diff --git a/dist/js/app.26bc1008.js b/dist/js/app.26bc1008.js new file mode 100644 index 0000000..f3d622e --- /dev/null +++ b/dist/js/app.26bc1008.js @@ -0,0 +1,3213 @@ +/******/ (function() { // webpackBootstrap +/******/ "use strict"; +/******/ var __webpack_modules__ = ({ + +/***/ 4677: +/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6768); +/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(144); +/* harmony import */ var _components_LoginView_vue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(3727); +/* harmony import */ var _components_DocumentedShell_vue__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(8409); +/* harmony import */ var _stores_session__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(926); + + + + + +/* harmony default export */ __webpack_exports__.A = ({ + __name: 'App', + setup(__props) { + const session = (0,_stores_session__WEBPACK_IMPORTED_MODULE_4__/* .useSessionStore */ .B)(); + (0,vue__WEBPACK_IMPORTED_MODULE_0__/* .onMounted */ .sV)(() => { + session.restore(); + }); + return (_ctx, _cache) => { + return !(0,vue__WEBPACK_IMPORTED_MODULE_1__/* .unref */ .R1)(session).isAuthenticated ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__/* .createBlock */ .Wv)(_components_LoginView_vue__WEBPACK_IMPORTED_MODULE_2__/* ["default"] */ .A, { + key: 0 + })) : ((0,vue__WEBPACK_IMPORTED_MODULE_0__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__/* .createBlock */ .Wv)(_components_DocumentedShell_vue__WEBPACK_IMPORTED_MODULE_3__/* ["default"] */ .A, { + key: 1 + })); + }; + } +}); + +/***/ }), + +/***/ 865: +/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5130); +/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6768); +/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(144); +/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(5187); +/* harmony import */ var _stores_session__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(926); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(2169); + +const _hoisted_1 = { + class: "documented-shell" +}; +const _hoisted_2 = { + class: "documented-rail" +}; +const _hoisted_3 = { + class: "documented-avatar" +}; +const _hoisted_4 = ["src"]; +const _hoisted_5 = { + key: 1 +}; +const _hoisted_6 = { + class: "documented-rail-copy" +}; +const _hoisted_7 = { + class: "documented-main" +}; +const _hoisted_8 = { + class: "documented-header" +}; +const _hoisted_9 = { + class: "documented-status" +}; +const _hoisted_10 = { + class: "documented-card" +}; +const _hoisted_11 = { + class: "documented-meta" +}; +const _hoisted_12 = { + key: 0, + class: "form-error" +}; +const _hoisted_13 = { + key: 1, + class: "form-info" +}; +const _hoisted_14 = { + class: "modal-actions" +}; +const _hoisted_15 = ["disabled"]; +const _hoisted_16 = ["disabled"]; + + + +/* harmony default export */ __webpack_exports__.A = ({ + __name: 'DocumentedShell', + setup(__props) { + const session = (0,_stores_session__WEBPACK_IMPORTED_MODULE_4__/* .useSessionStore */ .B)(); + const passwordDialogOpen = (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .ref */ .KR)(false); + const passwordSaving = (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .ref */ .KR)(false); + const passwordError = (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .ref */ .KR)(''); + const passwordNotice = (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .ref */ .KR)(''); + const passwordDraft = (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .reactive */ .Kh)({ + oldPassword: '', + newPassword: '', + confirmPassword: '' + }); + const initials = (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .computed */ .EW)(() => String(session.displayName || session.userId || 'U').slice(0, 1).toUpperCase()); + function resetPasswordDraft() { + passwordDraft.oldPassword = ''; + passwordDraft.newPassword = ''; + passwordDraft.confirmPassword = ''; + } + function openPasswordDialog() { + passwordError.value = ''; + passwordNotice.value = ''; + resetPasswordDraft(); + passwordDialogOpen.value = true; + } + function closePasswordDialog() { + if (passwordSaving.value) { + return; + } + passwordDialogOpen.value = false; + passwordError.value = ''; + passwordNotice.value = ''; + resetPasswordDraft(); + } + async function savePassword() { + passwordError.value = ''; + passwordNotice.value = ''; + if (!passwordDraft.oldPassword || !passwordDraft.newPassword || !passwordDraft.confirmPassword) { + passwordError.value = '请填写完整密码'; + return; + } + if (passwordDraft.newPassword !== passwordDraft.confirmPassword) { + passwordError.value = '两次输入的新密码不一致'; + return; + } + if (passwordDraft.oldPassword === passwordDraft.newPassword) { + passwordError.value = '新密码不能与当前密码相同'; + return; + } + passwordSaving.value = true; + try { + await session.changePassword({ + oldPassword: passwordDraft.oldPassword, + newPassword: passwordDraft.newPassword + }); + passwordNotice.value = '密码已修改,当前登录态会按文档要求失效。'; + } catch (error) { + passwordError.value = error.message || '密码修改失败'; + } finally { + passwordSaving.value = false; + } + } + async function logout() { + await session.logout(); + } + return (_ctx, _cache) => { + return (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementBlock */ .CE)("main", _hoisted_1, [(0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("aside", _hoisted_2, [(0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("div", _hoisted_3, [(0,vue__WEBPACK_IMPORTED_MODULE_2__/* .unref */ .R1)(session).portrait ? ((0,vue__WEBPACK_IMPORTED_MODULE_1__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementBlock */ .CE)("img", { + key: 0, + src: (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .unref */ .R1)(session).portrait, + alt: "" + }, null, 8, _hoisted_4)) : ((0,vue__WEBPACK_IMPORTED_MODULE_1__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementBlock */ .CE)("span", _hoisted_5, (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .toDisplayString */ .v_)(initials.value), 1))]), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("div", _hoisted_6, [(0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("strong", null, (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .toDisplayString */ .v_)((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .unref */ .R1)(session).displayName || (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .unref */ .R1)(session).userId), 1), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("small", null, (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .toDisplayString */ .v_)((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .unref */ .R1)(session).userId), 1)]), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("button", { + class: "documented-rail-button", + type: "button", + onClick: openPasswordDialog + }, "修改密码"), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("button", { + class: "documented-rail-button ghost", + type: "button", + onClick: logout + }, "退出登录")]), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("section", _hoisted_7, [(0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("header", _hoisted_8, [_cache[4] || (_cache[4] = (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("div", null, [(0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("h1", null, "文档对接模式"), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("p", null, "当前界面只保留这两份文档已经明确提供的能力。")], -1)), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("div", _hoisted_9, [_cache[3] || (_cache[3] = (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("span", null, "连接状态", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("strong", null, (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .toDisplayString */ .v_)((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .unref */ .R1)(session).connectionStatusText || '已登录'), 1)])]), _cache[11] || (_cache[11] = (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createStaticVNode */ .Fv)("

已按文档接入

当前按 1 比 1 已停用

下面这些旧页面功能已经不再走本地 PHP 或旧接口,因为你给的文档里没有对应定义。

", 2)), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("section", _hoisted_10, [_cache[10] || (_cache[10] = (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("h2", null, "当前登录态", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("dl", _hoisted_11, [(0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("div", null, [_cache[5] || (_cache[5] = (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("dt", null, "用户 ID", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("dd", null, (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .toDisplayString */ .v_)((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .unref */ .R1)(session).userId || '-'), 1)]), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("div", null, [_cache[6] || (_cache[6] = (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("dt", null, "显示名", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("dd", null, (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .toDisplayString */ .v_)((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .unref */ .R1)(session).displayName || '-'), 1)]), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("div", null, [_cache[7] || (_cache[7] = (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("dt", null, "IM Token", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("dd", null, (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .toDisplayString */ .v_)((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .unref */ .R1)(session).token ? '已保存' : '未保存'), 1)]), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("div", null, [_cache[8] || (_cache[8] = (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("dt", null, "App Server", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("dd", null, (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .toDisplayString */ .v_)((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .unref */ .R1)(_config__WEBPACK_IMPORTED_MODULE_5__/* .appConfig */ .U).appServer), 1)]), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("div", null, [_cache[9] || (_cache[9] = (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("dt", null, "扫码服务", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("dd", null, (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .toDisplayString */ .v_)((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .unref */ .R1)(_config__WEBPACK_IMPORTED_MODULE_5__/* .appConfig */ .U).qrAppServer), 1)])])])]), passwordDialogOpen.value ? ((0,vue__WEBPACK_IMPORTED_MODULE_1__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementBlock */ .CE)("div", { + key: 0, + class: "modal-backdrop", + onClick: (0,vue__WEBPACK_IMPORTED_MODULE_0__/* .withModifiers */ .D$)(closePasswordDialog, ["self"]) + }, [(0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("form", { + class: "modal-panel documented-password-panel", + onSubmit: (0,vue__WEBPACK_IMPORTED_MODULE_0__/* .withModifiers */ .D$)(savePassword, ["prevent"]) + }, [(0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("header", null, [_cache[12] || (_cache[12] = (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("strong", null, "修改密码", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("button", { + type: "button", + class: "icon-button", + title: "关闭", + onClick: closePasswordDialog + }, "×")]), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("label", null, [_cache[13] || (_cache[13] = (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("span", null, "当前密码", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .withDirectives */ .bo)((0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("input", { + "onUpdate:modelValue": _cache[0] || (_cache[0] = $event => passwordDraft.oldPassword = $event), + type: "password", + autocomplete: "current-password" + }, null, 512), [[vue__WEBPACK_IMPORTED_MODULE_0__/* .vModelText */ .Jo, passwordDraft.oldPassword]])]), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("label", null, [_cache[14] || (_cache[14] = (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("span", null, "新密码", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .withDirectives */ .bo)((0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("input", { + "onUpdate:modelValue": _cache[1] || (_cache[1] = $event => passwordDraft.newPassword = $event), + type: "password", + autocomplete: "new-password" + }, null, 512), [[vue__WEBPACK_IMPORTED_MODULE_0__/* .vModelText */ .Jo, passwordDraft.newPassword]])]), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("label", null, [_cache[15] || (_cache[15] = (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("span", null, "确认新密码", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .withDirectives */ .bo)((0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("input", { + "onUpdate:modelValue": _cache[2] || (_cache[2] = $event => passwordDraft.confirmPassword = $event), + type: "password", + autocomplete: "new-password" + }, null, 512), [[vue__WEBPACK_IMPORTED_MODULE_0__/* .vModelText */ .Jo, passwordDraft.confirmPassword]])]), passwordError.value ? ((0,vue__WEBPACK_IMPORTED_MODULE_1__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementBlock */ .CE)("p", _hoisted_12, (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .toDisplayString */ .v_)(passwordError.value), 1)) : (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createCommentVNode */ .Q3)("", true), passwordNotice.value ? ((0,vue__WEBPACK_IMPORTED_MODULE_1__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementBlock */ .CE)("p", _hoisted_13, (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .toDisplayString */ .v_)(passwordNotice.value), 1)) : (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createCommentVNode */ .Q3)("", true), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("div", _hoisted_14, [(0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("button", { + type: "button", + class: "ghost-inline-action", + disabled: passwordSaving.value, + onClick: closePasswordDialog + }, "取消", 8, _hoisted_15), (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createElementVNode */ .Lk)("button", { + class: "primary-inline-action", + disabled: passwordSaving.value + }, (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .toDisplayString */ .v_)(passwordSaving.value ? '保存中...' : '保存'), 9, _hoisted_16)])], 32)])) : (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .createCommentVNode */ .Q3)("", true)]); + }; + } +}); + +/***/ }), + +/***/ 8607: +/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +/* harmony import */ var core_js_modules_es_array_includes_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4423); +/* harmony import */ var core_js_modules_es_array_includes_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_array_includes_js__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(5130); +/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6768); +/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(144); +/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(5187); +/* harmony import */ var axios__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(2387); +/* harmony import */ var jr_qrcode__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(2789); +/* harmony import */ var jr_qrcode__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(jr_qrcode__WEBPACK_IMPORTED_MODULE_6__); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(2169); +/* harmony import */ var _stores_session__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(926); +/* harmony import */ var _services_imClient__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(5051); + + +const _hoisted_1 = { + class: "login-page" +}; +const _hoisted_2 = { + class: "login-panel" +}; +const _hoisted_3 = { + class: "brand-lockup" +}; +const _hoisted_4 = ["src", "alt"]; +const _hoisted_5 = { + class: "login-tabs register-tabs", + style: { + gridTemplateColumns: 'repeat(3, minmax(0, 1fr))' + } +}; +const _hoisted_6 = ["disabled"]; +const _hoisted_7 = ["src"]; +const _hoisted_8 = { + key: 1 +}; +const _hoisted_9 = { + key: 0, + class: "qr-scan-user" +}; +const _hoisted_10 = ["src"]; +const _hoisted_11 = { + key: 1 +}; +const _hoisted_12 = { + class: "qr-login-status" +}; +const _hoisted_13 = ["disabled"]; +const _hoisted_14 = ["autocomplete", "inputmode", "placeholder"]; +const _hoisted_15 = ["disabled"]; +const _hoisted_16 = { + key: 2, + class: "form-error" +}; +const _hoisted_17 = { + key: 3, + class: "form-info" +}; +const _hoisted_18 = { + class: "login-tabs register-tabs", + style: { + gridTemplateColumns: 'repeat(2, minmax(0, 1fr))' + } +}; +const _hoisted_19 = ["autocomplete", "inputmode", "placeholder"]; +const _hoisted_20 = { + key: 0 +}; +const _hoisted_21 = { + class: "code-row" +}; +const _hoisted_22 = ["disabled"]; +const _hoisted_23 = ["disabled"]; +const _hoisted_24 = { + key: 1, + class: "form-error" +}; +const _hoisted_25 = { + key: 2, + class: "form-info" +}; + + + + + + +const QR_CODE_PREFIX_PC_SESSION = 'wildfirechat://pcsession/'; +const defaultPlatformLogo = '/favicon.ico'; +/* harmony default export */ __webpack_exports__.A = ({ + __name: 'LoginView', + setup(__props) { + const session = (0,_stores_session__WEBPACK_IMPORTED_MODULE_8__/* .useSessionStore */ .B)(); + const view = (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .ref */ .KR)('login'); + const loginMode = (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .ref */ .KR)('phone'); + const loginAccount = (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .ref */ .KR)(''); + const loginPassword = (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .ref */ .KR)(''); + const registerType = (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .ref */ .KR)(1); + const registerForm = (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .reactive */ .Kh)({ + number: '', + code: '', + password: '', + confirmPassword: '', + invitationCode: '' + }); + const sendingCode = (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .ref */ .KR)(false); + const info = (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .ref */ .KR)(''); + const qrStatus = (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .ref */ .KR)('idle'); + const qrCodeImage = (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .ref */ .KR)(''); + const qrSessionToken = (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .ref */ .KR)(''); + const qrUserName = (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .ref */ .KR)(''); + const qrUserPortrait = (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .ref */ .KR)(''); + let qrGeneration = 0; + let qrExpireTimer = null; + const defaultPlatformName = _config__WEBPACK_IMPORTED_MODULE_7__/* .appConfig */ .U.appName; + const platformName = (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .ref */ .KR)(defaultPlatformName); + const platformLogo = (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .ref */ .KR)(defaultPlatformLogo); + const canSubmitLogin = (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .computed */ .EW)(() => loginAccount.value.length >= 3 && loginPassword.value.length >= 1); + const canRequestEmailCode = (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .computed */ .EW)(() => registerType.value === 2 && /\S+@\S+\.\S+/.test(registerForm.number)); + const canSubmitRegister = (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .computed */ .EW)(() => { + if (registerForm.password.length < 1 || registerForm.password !== registerForm.confirmPassword) { + return false; + } + if (registerType.value === 2) { + return canRequestEmailCode.value && registerForm.code.length >= 1; + } + return registerForm.number.length >= 5; + }); + const qrUserInitial = (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .computed */ .EW)(() => String(qrUserName.value || 'U').slice(0, 1).toUpperCase()); + const qrStatusTitle = (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .computed */ .EW)(() => { + if (session.connecting || qrStatus.value === 'confirmed') { + return '正在进入'; + } + if (qrStatus.value === 'loading') { + return '正在生成二维码'; + } + if (qrStatus.value === 'scanned') { + return qrUserName.value ? `${qrUserName.value} 已扫码` : '已扫码,待确认'; + } + if (qrStatus.value === 'expired') { + return '二维码已过期'; + } + if (qrStatus.value === 'error') { + return '二维码加载失败'; + } + return '扫码登录'; + }); + const qrStatusText = (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .computed */ .EW)(() => { + if (session.connecting || qrStatus.value === 'confirmed') { + return '登录信息已确认,正在建立连接'; + } + if (qrStatus.value === 'loading') { + return '正在创建扫码登录会话'; + } + if (qrStatus.value === 'scanned') { + return '请在手机 App 上确认登录'; + } + if (qrStatus.value === 'expired') { + return '请刷新二维码后重新扫码'; + } + if (qrStatus.value === 'error') { + return '请稍后重试'; + } + return '使用已登录的手机 App 扫描二维码'; + }); + const qrRefreshText = (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .computed */ .EW)(() => { + if (qrStatus.value === 'loading') { + return '生成中...'; + } + if (qrStatus.value === 'expired' || qrStatus.value === 'error') { + return '重新生成二维码'; + } + return '刷新二维码'; + }); + (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .onMounted */ .sV)(() => { + loadPlatformConfig(); + }); + (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .onBeforeUnmount */ .xo)(() => { + stopQrLogin(); + }); + function clearFormMessage() { + session.error = ''; + info.value = ''; + } + function setLoginMode(mode) { + loginMode.value = mode; + loginAccount.value = ''; + loginPassword.value = ''; + clearFormMessage(); + if (mode === 'qr') { + startQrLogin(); + return; + } + stopQrLogin(); + } + async function submitLogin() { + clearFormMessage(); + const type = loginMode.value === 'email' ? 2 : 1; + await session.loginWithPassword(loginAccount.value, loginPassword.value, type); + } + function switchRegisterType(type) { + registerType.value = type; + registerForm.number = ''; + registerForm.code = ''; + registerForm.password = ''; + registerForm.confirmPassword = ''; + registerForm.invitationCode = ''; + clearFormMessage(); + } + async function requestEmailCode() { + clearFormMessage(); + sendingCode.value = true; + try { + await _services_imClient__WEBPACK_IMPORTED_MODULE_9__/* .imClient */ .wD.requestAuthCode(registerForm.number); + info.value = '邮箱验证码已发送,请在当前注册表单中填写后继续。'; + } catch (error) { + session.error = error.message || '验证码发送失败'; + } finally { + sendingCode.value = false; + } + } + async function submitRegister() { + clearFormMessage(); + if (registerForm.password !== registerForm.confirmPassword) { + session.error = '两次输入的密码不一致'; + return; + } + const result = await session.register({ + type: registerType.value, + number: registerForm.number, + code: registerType.value === 2 ? registerForm.code : '', + password: registerForm.password, + invitationCode: registerForm.invitationCode + }); + if (result) { + info.value = '注册成功,正在进入系统。'; + } + } + function openRegister() { + clearFormMessage(); + stopQrLogin(); + view.value = 'register'; + } + function backToLogin() { + clearFormMessage(); + loginMode.value = 'phone'; + stopQrLogin(); + view.value = 'login'; + } + function resetQrState() { + qrStatus.value = 'idle'; + qrCodeImage.value = ''; + qrSessionToken.value = ''; + qrUserName.value = ''; + qrUserPortrait.value = ''; + } + function clearQrExpireTimer() { + if (qrExpireTimer) { + window.clearTimeout(qrExpireTimer); + qrExpireTimer = null; + } + } + function stopQrLogin() { + qrGeneration += 1; + clearQrExpireTimer(); + resetQrState(); + } + function refreshQrLogin() { + startQrLogin(true); + } + async function startQrLogin(force = false) { + if (loginMode.value !== 'qr') { + return; + } + if (!force && qrSessionToken.value && qrCodeImage.value && !['expired', 'error'].includes(qrStatus.value)) { + return; + } + qrGeneration += 1; + const generation = qrGeneration; + clearQrExpireTimer(); + clearFormMessage(); + qrStatus.value = 'loading'; + qrCodeImage.value = ''; + qrSessionToken.value = ''; + qrUserName.value = ''; + qrUserPortrait.value = ''; + try { + const pcSession = await _services_imClient__WEBPACK_IMPORTED_MODULE_9__/* .imClient */ .wD.createPcSession(); + if (generation !== qrGeneration || loginMode.value !== 'qr') { + return; + } + const token = String(pcSession.token || ''); + if (!token) { + throw new Error('服务端没有返回扫码 token'); + } + qrSessionToken.value = token; + qrCodeImage.value = jr_qrcode__WEBPACK_IMPORTED_MODULE_6___default().getQrBase64(QR_CODE_PREFIX_PC_SESSION + token); + qrStatus.value = 'waiting'; + const expireMs = Math.max(30000, Math.min(Number(pcSession.expired || 300000), 300000)); + qrExpireTimer = window.setTimeout(() => { + if (generation === qrGeneration && qrStatus.value !== 'confirmed' && !session.isAuthenticated) { + qrStatus.value = 'expired'; + } + }, expireMs); + pollQrLogin(generation, token); + } catch (error) { + if (generation !== qrGeneration) { + return; + } + qrStatus.value = 'error'; + session.error = error.message || '二维码加载失败'; + } + } + async function pollQrLogin(generation, token) { + try { + while (generation === qrGeneration && loginMode.value === 'qr' && !session.isAuthenticated) { + const payload = await _services_imClient__WEBPACK_IMPORTED_MODULE_9__/* .imClient */ .wD.loginWithPcSession(token); + if (generation !== qrGeneration || loginMode.value !== 'qr') { + return; + } + const code = Number(payload.code); + const data = payload.data || payload.result || {}; + const status = Number(data.status); + if (code === 0 && data.imToken) { + qrStatus.value = 'confirmed'; + clearQrExpireTimer(); + await session.completePcSessionLogin(data); + return; + } + if (code === 20042 || status === 1 || status === 3) { + qrStatus.value = 'scanned'; + qrUserName.value = data.userName || data.displayName || qrUserName.value; + qrUserPortrait.value = data.portrait || qrUserPortrait.value; + await delay(1000); + continue; + } + if (status === 0 || code === 13) { + qrStatus.value = 'waiting'; + await delay(1000); + continue; + } + if (status === 4) { + qrStatus.value = 'expired'; + session.error = payload.message || '扫码登录已取消'; + return; + } + if (payload.message) { + throw new Error(payload.message); + } + await delay(1000); + } + } catch (error) { + if (generation === qrGeneration && loginMode.value === 'qr' && !session.isAuthenticated) { + qrStatus.value = 'error'; + session.error = error.message || '扫码登录失败'; + } + } + } + function delay(ms) { + return new Promise(resolve => window.setTimeout(resolve, ms)); + } + async function loadPlatformConfig() { + try { + const response = await axios__WEBPACK_IMPORTED_MODULE_5__/* ["default"] */ .A.get(_config__WEBPACK_IMPORTED_MODULE_7__/* .appConfig */ .U.platformConfigApi, { + withCredentials: false + }); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '平台配置加载失败'); + } + const config = response.data?.result || {}; + const name = String(config.platformName || '').trim(); + const logo = String(config.logoUrl || config.logo || '').trim(); + const version = String(config.version || '').trim(); + platformName.value = name || defaultPlatformName; + platformLogo.value = resolveBrandLogo(logo, version); + } catch (error) { + platformName.value = defaultPlatformName; + platformLogo.value = defaultPlatformLogo; + } + applyDocumentBrand(); + } + function resolveBrandLogo(value, version = '') { + const logo = String(value || '').trim(); + if (!logo) { + return defaultPlatformLogo; + } + if (/^(https?:|data:|blob:)/i.test(logo)) { + return appendBrandVersion(logo, version); + } + if (logo.startsWith('//')) { + return appendBrandVersion(`${window.location.protocol}${logo}`, version); + } + const base = logo.startsWith('/') ? _config__WEBPACK_IMPORTED_MODULE_7__/* .appConfig */ .U.adminServer : `${_config__WEBPACK_IMPORTED_MODULE_7__/* .appConfig */ .U.adminServer}/`; + return appendBrandVersion(new URL(logo, base).toString(), version); + } + function appendBrandVersion(url, version) { + if (!version || /^(data:|blob:)/i.test(url)) { + return url; + } + const parsed = new URL(url, window.location.href); + parsed.searchParams.set('v', version); + return parsed.toString(); + } + function applyDocumentBrand() { + if (typeof document === 'undefined') { + return; + } + document.title = platformName.value || defaultPlatformName; + let icon = document.querySelector('link[rel="icon"]'); + if (!icon) { + icon = document.createElement('link'); + icon.setAttribute('rel', 'icon'); + document.head.appendChild(icon); + } + icon.setAttribute('href', platformLogo.value || defaultPlatformLogo); + } + function handleBrandLogoError() { + if (platformLogo.value !== defaultPlatformLogo) { + platformLogo.value = defaultPlatformLogo; + applyDocumentBrand(); + } + } + return (_ctx, _cache) => { + return (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)("main", _hoisted_1, [(0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("section", _hoisted_2, [(0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("div", _hoisted_3, [(0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("img", { + class: "brand-mark", + src: platformLogo.value, + alt: `${platformName.value} logo`, + onError: handleBrandLogoError + }, null, 40, _hoisted_4), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("div", null, [(0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("h1", null, (0,vue__WEBPACK_IMPORTED_MODULE_4__/* .toDisplayString */ .v_)(platformName.value), 1)])]), view.value === 'login' ? ((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)("form", { + key: 0, + class: "login-form", + onSubmit: (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .withModifiers */ .D$)(submitLogin, ["prevent"]) + }, [(0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("div", _hoisted_5, [(0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("button", { + type: "button", + class: (0,vue__WEBPACK_IMPORTED_MODULE_4__/* .normalizeClass */ .C4)({ + active: loginMode.value === 'qr' + }), + onClick: _cache[0] || (_cache[0] = $event => setLoginMode('qr')) + }, "扫码登录", 2), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("button", { + type: "button", + class: (0,vue__WEBPACK_IMPORTED_MODULE_4__/* .normalizeClass */ .C4)({ + active: loginMode.value === 'phone' + }), + onClick: _cache[1] || (_cache[1] = $event => setLoginMode('phone')) + }, "手机号", 2), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("button", { + type: "button", + class: (0,vue__WEBPACK_IMPORTED_MODULE_4__/* .normalizeClass */ .C4)({ + active: loginMode.value === 'email' + }), + onClick: _cache[2] || (_cache[2] = $event => setLoginMode('email')) + }, "邮箱", 2)]), loginMode.value === 'qr' ? ((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)(vue__WEBPACK_IMPORTED_MODULE_2__/* .Fragment */ .FK, { + key: 0 + }, [(0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("div", { + class: (0,vue__WEBPACK_IMPORTED_MODULE_4__/* .normalizeClass */ .C4)(["qr-login-card", { + scanned: qrStatus.value === 'scanned', + expired: qrStatus.value === 'expired', + error: qrStatus.value === 'error' + }]) + }, [(0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("button", { + type: "button", + class: "qr-code-button", + disabled: qrStatus.value === 'loading' || (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .unref */ .R1)(session).connecting, + onClick: refreshQrLogin + }, [qrCodeImage.value ? ((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)("img", { + key: 0, + src: qrCodeImage.value, + alt: "扫码登录二维码" + }, null, 8, _hoisted_7)) : ((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)("span", _hoisted_8, (0,vue__WEBPACK_IMPORTED_MODULE_4__/* .toDisplayString */ .v_)(qrStatus.value === 'loading' ? '生成中' : '点击刷新'), 1))], 8, _hoisted_6), qrStatus.value === 'scanned' ? ((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)("div", _hoisted_9, [qrUserPortrait.value ? ((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)("img", { + key: 0, + src: qrUserPortrait.value, + alt: "" + }, null, 8, _hoisted_10)) : ((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)("span", _hoisted_11, (0,vue__WEBPACK_IMPORTED_MODULE_4__/* .toDisplayString */ .v_)(qrUserInitial.value), 1))])) : (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createCommentVNode */ .Q3)("", true)], 2), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("div", _hoisted_12, [(0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("strong", null, (0,vue__WEBPACK_IMPORTED_MODULE_4__/* .toDisplayString */ .v_)(qrStatusTitle.value), 1), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("span", null, (0,vue__WEBPACK_IMPORTED_MODULE_4__/* .toDisplayString */ .v_)(qrStatusText.value), 1)]), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("button", { + type: "button", + class: "primary-action", + disabled: qrStatus.value === 'loading' || (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .unref */ .R1)(session).connecting, + onClick: refreshQrLogin + }, (0,vue__WEBPACK_IMPORTED_MODULE_4__/* .toDisplayString */ .v_)(qrRefreshText.value), 9, _hoisted_13)], 64)) : ((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)(vue__WEBPACK_IMPORTED_MODULE_2__/* .Fragment */ .FK, { + key: 1 + }, [(0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("label", null, [(0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("span", null, (0,vue__WEBPACK_IMPORTED_MODULE_4__/* .toDisplayString */ .v_)(loginMode.value === 'phone' ? '手机号' : '邮箱'), 1), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .withDirectives */ .bo)((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("input", { + "onUpdate:modelValue": _cache[3] || (_cache[3] = $event => loginAccount.value = $event), + autocomplete: loginMode.value === 'phone' ? 'username' : 'email', + inputmode: loginMode.value === 'phone' ? 'tel' : 'email', + placeholder: loginMode.value === 'phone' ? '请输入手机号' : '请输入邮箱' + }, null, 8, _hoisted_14), [[vue__WEBPACK_IMPORTED_MODULE_1__/* .vModelText */ .Jo, loginAccount.value, void 0, { + trim: true + }]])]), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("label", null, [_cache[12] || (_cache[12] = (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("span", null, "密码", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .withDirectives */ .bo)((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("input", { + "onUpdate:modelValue": _cache[4] || (_cache[4] = $event => loginPassword.value = $event), + autocomplete: "current-password", + type: "password", + placeholder: "请输入密码" + }, null, 512), [[vue__WEBPACK_IMPORTED_MODULE_1__/* .vModelText */ .Jo, loginPassword.value]])]), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("button", { + class: "primary-action", + disabled: (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .unref */ .R1)(session).connecting || !canSubmitLogin.value + }, (0,vue__WEBPACK_IMPORTED_MODULE_4__/* .toDisplayString */ .v_)((0,vue__WEBPACK_IMPORTED_MODULE_3__/* .unref */ .R1)(session).connecting ? '登录中...' : '登录'), 9, _hoisted_15)], 64)), (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .unref */ .R1)(session).error ? ((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)("p", _hoisted_16, (0,vue__WEBPACK_IMPORTED_MODULE_4__/* .toDisplayString */ .v_)((0,vue__WEBPACK_IMPORTED_MODULE_3__/* .unref */ .R1)(session).error), 1)) : (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createCommentVNode */ .Q3)("", true), info.value ? ((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)("p", _hoisted_17, (0,vue__WEBPACK_IMPORTED_MODULE_4__/* .toDisplayString */ .v_)(info.value), 1)) : (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createCommentVNode */ .Q3)("", true)], 32)) : ((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)("form", { + key: 1, + class: "login-form", + onSubmit: (0,vue__WEBPACK_IMPORTED_MODULE_1__/* .withModifiers */ .D$)(submitRegister, ["prevent"]) + }, [(0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("div", _hoisted_18, [(0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("button", { + type: "button", + class: (0,vue__WEBPACK_IMPORTED_MODULE_4__/* .normalizeClass */ .C4)({ + active: registerType.value === 1 + }), + onClick: _cache[5] || (_cache[5] = $event => switchRegisterType(1)) + }, "手机号注册", 2), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("button", { + type: "button", + class: (0,vue__WEBPACK_IMPORTED_MODULE_4__/* .normalizeClass */ .C4)({ + active: registerType.value === 2 + }), + onClick: _cache[6] || (_cache[6] = $event => switchRegisterType(2)) + }, "邮箱注册", 2)]), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("label", null, [(0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("span", null, (0,vue__WEBPACK_IMPORTED_MODULE_4__/* .toDisplayString */ .v_)(registerType.value === 1 ? '手机号' : '邮箱'), 1), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .withDirectives */ .bo)((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("input", { + "onUpdate:modelValue": _cache[7] || (_cache[7] = $event => registerForm.number = $event), + autocomplete: registerType.value === 1 ? 'tel' : 'email', + inputmode: registerType.value === 1 ? 'tel' : 'email', + placeholder: registerType.value === 1 ? '请输入手机号' : '请输入邮箱' + }, null, 8, _hoisted_19), [[vue__WEBPACK_IMPORTED_MODULE_1__/* .vModelText */ .Jo, registerForm.number, void 0, { + trim: true + }]])]), registerType.value === 2 ? ((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)("label", _hoisted_20, [_cache[13] || (_cache[13] = (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("span", null, "邮箱验证码", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("div", _hoisted_21, [(0,vue__WEBPACK_IMPORTED_MODULE_2__/* .withDirectives */ .bo)((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("input", { + "onUpdate:modelValue": _cache[8] || (_cache[8] = $event => registerForm.code = $event), + autocomplete: "one-time-code", + inputmode: "numeric", + placeholder: "请输入邮箱验证码" + }, null, 512), [[vue__WEBPACK_IMPORTED_MODULE_1__/* .vModelText */ .Jo, registerForm.code, void 0, { + trim: true + }]]), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("button", { + type: "button", + class: "code-action", + disabled: sendingCode.value || !canRequestEmailCode.value, + onClick: requestEmailCode + }, (0,vue__WEBPACK_IMPORTED_MODULE_4__/* .toDisplayString */ .v_)(sendingCode.value ? '发送中' : '发送验证码'), 9, _hoisted_22)])])) : (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createCommentVNode */ .Q3)("", true), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("label", null, [_cache[14] || (_cache[14] = (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("span", null, "密码", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .withDirectives */ .bo)((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("input", { + "onUpdate:modelValue": _cache[9] || (_cache[9] = $event => registerForm.password = $event), + autocomplete: "new-password", + type: "password", + placeholder: "请输入密码" + }, null, 512), [[vue__WEBPACK_IMPORTED_MODULE_1__/* .vModelText */ .Jo, registerForm.password]])]), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("label", null, [_cache[15] || (_cache[15] = (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("span", null, "确认密码", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .withDirectives */ .bo)((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("input", { + "onUpdate:modelValue": _cache[10] || (_cache[10] = $event => registerForm.confirmPassword = $event), + autocomplete: "new-password", + type: "password", + placeholder: "请再次输入密码" + }, null, 512), [[vue__WEBPACK_IMPORTED_MODULE_1__/* .vModelText */ .Jo, registerForm.confirmPassword]])]), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("label", null, [_cache[16] || (_cache[16] = (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("span", null, "邀请码", -1)), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .withDirectives */ .bo)((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("input", { + "onUpdate:modelValue": _cache[11] || (_cache[11] = $event => registerForm.invitationCode = $event), + autocomplete: "off", + placeholder: "没有可留空" + }, null, 512), [[vue__WEBPACK_IMPORTED_MODULE_1__/* .vModelText */ .Jo, registerForm.invitationCode, void 0, { + trim: true + }]])]), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementVNode */ .Lk)("button", { + class: "primary-action", + disabled: (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .unref */ .R1)(session).connecting || !canSubmitRegister.value + }, (0,vue__WEBPACK_IMPORTED_MODULE_4__/* .toDisplayString */ .v_)((0,vue__WEBPACK_IMPORTED_MODULE_3__/* .unref */ .R1)(session).connecting ? '注册中...' : '注册并登录'), 9, _hoisted_23), (0,vue__WEBPACK_IMPORTED_MODULE_3__/* .unref */ .R1)(session).error ? ((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)("p", _hoisted_24, (0,vue__WEBPACK_IMPORTED_MODULE_4__/* .toDisplayString */ .v_)((0,vue__WEBPACK_IMPORTED_MODULE_3__/* .unref */ .R1)(session).error), 1)) : (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createCommentVNode */ .Q3)("", true), info.value ? ((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)("p", _hoisted_25, (0,vue__WEBPACK_IMPORTED_MODULE_4__/* .toDisplayString */ .v_)(info.value), 1)) : (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createCommentVNode */ .Q3)("", true)], 32)), view.value === 'login' ? ((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)("button", { + key: 2, + class: "ghost-action", + type: "button", + onClick: openRegister + }, " 新用户注册 ")) : ((0,vue__WEBPACK_IMPORTED_MODULE_2__/* .openBlock */ .uX)(), (0,vue__WEBPACK_IMPORTED_MODULE_2__/* .createElementBlock */ .CE)("button", { + key: 3, + class: "ghost-action", + type: "button", + onClick: backToLogin + }, " 返回登录 "))])]); + }; + } +}); + +/***/ }), + +/***/ 2169: +/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +const currentHost = window.location.hostname || '127.0.0.1'; +const runtimeConfig = window.JC_WEB_IM_CONFIG || {}; +const appServerOrigin = `${window.location.protocol}//${currentHost}:8888`; +const qrAppServerOrigin = `${window.location.protocol}//${currentHost}:18888`; +const adminServerOrigin = `${window.location.protocol}//${currentHost}:8024`; +const appConfig = { + appName: 'Web IM', + adminServer: runtimeConfig.adminServer || undefined || adminServerOrigin, + appServer: runtimeConfig.appServer || undefined || appServerOrigin, + qrAppServer: runtimeConfig.qrAppServer || undefined || qrAppServerOrigin, + platformConfigApi: runtimeConfig.platformConfigApi || undefined || ( false ? 0 : '/api/platform_config.php'), + registerConfigApi: runtimeConfig.registerConfigApi || undefined || `${adminServerOrigin}/api/register-config`, + registerPhoneExistsApi: runtimeConfig.registerPhoneExistsApi || undefined || `${adminServerOrigin}/api/register-phone-exists`, + contactsApi: runtimeConfig.contactsApi || undefined || ( false ? 0 : '/api/contacts.php'), + updateAliasApi: runtimeConfig.updateAliasApi || undefined || ( false ? 0 : '/api/update_alias.php'), + addContactApi: runtimeConfig.addContactApi || undefined || ( false ? 0 : '/api/add_contact.php'), + conversationActionsApi: runtimeConfig.conversationActionsApi || undefined || ( false ? 0 : '/api/conversation_actions.php'), + sendMessageApi: runtimeConfig.sendMessageApi || undefined || ( false ? 0 : '/api/send_message.php'), + messageActionApi: runtimeConfig.messageActionApi || undefined || ( false ? 0 : '/api/message_action.php'), + messageDetailApi: runtimeConfig.messageDetailApi || undefined || ( false ? 0 : '/api/message_detail.php'), + messagesApi: runtimeConfig.messagesApi || undefined || ( false ? 0 : '/api/messages.php'), + profileApi: runtimeConfig.profileApi || undefined || ( false ? 0 : '/api/profile.php'), + changePasswordApi: runtimeConfig.changePasswordApi || undefined || `${appServerOrigin}/auth/changePassword`, + logoutApi: runtimeConfig.logoutApi || undefined || `${appServerOrigin}/auth/logout`, + imServerHost: runtimeConfig.imServerHost || undefined || currentHost, + imRoutePort: Number(runtimeConfig.imRoutePort || undefined || 18000), + useWss: runtimeConfig.useWss ?? undefined === 'true', + wsPath: runtimeConfig.wsPath || undefined || '/ws', + wsUrl: runtimeConfig.wsUrl || undefined || '', + sdkMode: runtimeConfig.sdkMode || undefined || 'http', + syncIntervalMs: Number(runtimeConfig.syncIntervalMs || undefined || 300), + platform: 5, + clientIdStorageKey: 'qmt-web-im-client-id', + authTokenStoragePrefix: 'qmt-web-im-auth-token-' +}; +/* harmony export */ __webpack_require__.d(__webpack_exports__, [ +/* harmony export */ "U", 0, /* binding */ appConfig +/* harmony export */ ]); + + +/***/ }), + +/***/ 1406: +/***/ (function(__unused_webpack_module, __unused_webpack___webpack_exports__, __webpack_require__) { + +/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5130); +/* harmony import */ var pinia__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2261); +/* harmony import */ var _App_vue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(7067); + + + + +(0,vue__WEBPACK_IMPORTED_MODULE_0__/* .createApp */ .Ef)(_App_vue__WEBPACK_IMPORTED_MODULE_2__/* ["default"] */ .A).use((0,pinia__WEBPACK_IMPORTED_MODULE_1__/* .createPinia */ .Ey)()).mount('#app'); + +/***/ }), + +/***/ 9311: +/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ Ln: function() { return /* binding */ appServerPost; }, +/* harmony export */ Pd: function() { return /* binding */ getStoredAppAuth; }, +/* harmony export */ eG: function() { return /* binding */ persistAppAuth; }, +/* harmony export */ u$: function() { return /* binding */ clearStoredAppAuth; } +/* harmony export */ }); +/* unused harmony exports appServerRequest, appServerGet */ +/* harmony import */ var core_js_modules_es_iterator_constructor_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(8111); +/* harmony import */ var core_js_modules_es_iterator_constructor_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_constructor_js__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var core_js_modules_es_iterator_for_each_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(7588); +/* harmony import */ var core_js_modules_es_iterator_for_each_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_for_each_js__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var core_js_modules_es_typed_array_with_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(9577); +/* harmony import */ var core_js_modules_es_typed_array_with_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_typed_array_with_js__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var core_js_modules_es_uint8_array_set_from_base64_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6632); +/* harmony import */ var core_js_modules_es_uint8_array_set_from_base64_js__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_uint8_array_set_from_base64_js__WEBPACK_IMPORTED_MODULE_3__); +/* harmony import */ var core_js_modules_es_uint8_array_set_from_hex_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(4226); +/* harmony import */ var core_js_modules_es_uint8_array_set_from_hex_js__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_uint8_array_set_from_hex_js__WEBPACK_IMPORTED_MODULE_4__); +/* harmony import */ var core_js_modules_es_uint8_array_to_base64_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(9486); +/* harmony import */ var core_js_modules_es_uint8_array_to_base64_js__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_uint8_array_to_base64_js__WEBPACK_IMPORTED_MODULE_5__); +/* harmony import */ var core_js_modules_es_uint8_array_to_hex_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(456); +/* harmony import */ var core_js_modules_es_uint8_array_to_hex_js__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_uint8_array_to_hex_js__WEBPACK_IMPORTED_MODULE_6__); +/* harmony import */ var core_js_modules_web_dom_exception_stack_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(4979); +/* harmony import */ var core_js_modules_web_dom_exception_stack_js__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_web_dom_exception_stack_js__WEBPACK_IMPORTED_MODULE_7__); +/* harmony import */ var axios__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(2387); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(2169); + + + + + + + + + + +const AES_KEY = 'xczx2026!@#secKe'; +const ENCRYPTION_EXCLUDE_PATHS = ['/avatar/', '/f/', '/amr2mp3', '/appVersion/check']; +const AUTH_STORAGE_KEY = 'qmt-web-im-app-auth-v1'; +const AUTH_ERROR_CODE = 13; +const REFRESH_ERROR_CODE = 20045; +let importedCryptoKeyPromise = null; +let refreshPromise = null; +function storageScope() { + return _config__WEBPACK_IMPORTED_MODULE_9__/* .appConfig */ .U.appServer || ''; +} +function readAuthStore() { + try { + return JSON.parse(localStorage.getItem(AUTH_STORAGE_KEY) || '{}'); + } catch (error) { + return {}; + } +} +function writeAuthStore(store) { + localStorage.setItem(AUTH_STORAGE_KEY, JSON.stringify(store)); +} +function normalizeAuthBundle(bundle = {}) { + const accessToken = String(bundle.accessToken || '').trim(); + const refreshToken = String(bundle.refreshToken || '').trim(); + if (!accessToken || !refreshToken) { + return null; + } + return { + accessToken, + refreshToken, + tokenType: String(bundle.tokenType || 'Bearer').trim() || 'Bearer', + accessTokenExpireIn: Number(bundle.accessTokenExpireIn || 0) || 0, + accessTokenExpireAt: Number(bundle.accessTokenExpireAt || 0) || 0, + refreshTokenExpireIn: Number(bundle.refreshTokenExpireIn || 0) || 0, + refreshTokenExpireAt: Number(bundle.refreshTokenExpireAt || 0) || 0 + }; +} +function getStoredAppAuth() { + const store = readAuthStore(); + return normalizeAuthBundle(store[storageScope()]); +} +function persistAppAuth(bundle) { + const normalized = normalizeAuthBundle(bundle); + const store = readAuthStore(); + if (!normalized) { + delete store[storageScope()]; + writeAuthStore(store); + return null; + } + store[storageScope()] = normalized; + writeAuthStore(store); + return normalized; +} +function clearStoredAppAuth() { + persistAppAuth(null); +} +function hasFormData(value) { + return typeof FormData !== 'undefined' && value instanceof FormData; +} +function absoluteUrl(url) { + return new URL(url, window.location.origin); +} +function shouldEncrypt(url) { + const pathname = absoluteUrl(url).pathname; + return !ENCRYPTION_EXCLUDE_PATHS.some(path => pathname === path || pathname.startsWith(path)); +} +function shouldAttachAuth(config) { + return !config.skipAuth; +} +function textEncoder() { + return new TextEncoder(); +} +function textDecoder() { + return new TextDecoder(); +} +function bytesToBase64(bytes) { + let binary = ''; + bytes.forEach(byte => { + binary += String.fromCharCode(byte); + }); + return btoa(binary); +} +function base64ToBytes(base64) { + const binary = atob(base64); + const bytes = new Uint8Array(binary.length); + for (let index = 0; index < binary.length; index += 1) { + bytes[index] = binary.charCodeAt(index); + } + return bytes; +} +async function getCryptoKey() { + if (!importedCryptoKeyPromise) { + importedCryptoKeyPromise = window.crypto.subtle.importKey('raw', textEncoder().encode(AES_KEY), { + name: 'AES-CBC' + }, false, ['encrypt', 'decrypt']); + } + return importedCryptoKeyPromise; +} +async function encryptPayload(plainText) { + const iv = window.crypto.getRandomValues(new Uint8Array(16)); + const key = await getCryptoKey(); + const encrypted = await window.crypto.subtle.encrypt({ + name: 'AES-CBC', + iv + }, key, textEncoder().encode(plainText)); + return `${bytesToBase64(iv)}:${bytesToBase64(new Uint8Array(encrypted))}`; +} +async function decryptPayload(cipherText) { + const [ivBase64, encryptedBase64] = String(cipherText || '').split(':', 2); + if (!ivBase64 || !encryptedBase64) { + throw new Error('Encrypted payload format is invalid'); + } + const key = await getCryptoKey(); + const decrypted = await window.crypto.subtle.decrypt({ + name: 'AES-CBC', + iv: base64ToBytes(ivBase64) + }, key, base64ToBytes(encryptedBase64)); + return textDecoder().decode(decrypted); +} +function normalizeBusinessResponse(payload) { + if (payload && typeof payload === 'object' && !Array.isArray(payload)) { + return payload; + } + return { + code: 0, + message: 'success', + data: payload + }; +} +async function decodeResponsePayload(response) { + const encrypted = String(response.headers?.['x-encrypted'] || '').toLowerCase() === 'true'; + if (encrypted && typeof response.data === 'string') { + const plainText = await decryptPayload(response.data); + response.data = normalizeBusinessResponse(JSON.parse(plainText)); + return response; + } + response.data = normalizeBusinessResponse(response.data); + return response; +} +const client = axios__WEBPACK_IMPORTED_MODULE_8__/* ["default"] */ .A.create({ + withCredentials: false +}); +client.interceptors.request.use(async config => { + const nextConfig = { + ...config + }; + const url = absoluteUrl(nextConfig.url || '').toString(); + if (shouldAttachAuth(nextConfig)) { + const auth = getStoredAppAuth(); + if (auth?.accessToken) { + nextConfig.headers = { + ...(nextConfig.headers || {}), + Authorization: `${auth.tokenType || 'Bearer'} ${auth.accessToken}` + }; + } + } + if (!hasFormData(nextConfig.data) && nextConfig.method?.toLowerCase() !== 'get' && shouldEncrypt(url)) { + const plainBody = JSON.stringify(nextConfig.data ?? {}); + nextConfig.data = await encryptPayload(plainBody); + nextConfig.headers = { + ...(nextConfig.headers || {}), + 'Content-Type': 'text/plain;charset=UTF-8', + 'X-Timestamp': `${Math.floor(Date.now() / 1000)}` + }; + } + return nextConfig; +}); +client.interceptors.response.use(async response => decodeResponsePayload(response)); +async function refreshAccessToken() { + if (refreshPromise) { + return refreshPromise; + } + const auth = getStoredAppAuth(); + if (!auth?.refreshToken) { + clearStoredAppAuth(); + throw new Error('登录状态已失效,请重新登录'); + } + refreshPromise = client.post(`${_config__WEBPACK_IMPORTED_MODULE_9__/* .appConfig */ .U.appServer}/auth/refreshToken`, { + refreshToken: auth.refreshToken + }, { + skipAuth: true + }).then(response => { + const payload = response.data || {}; + if (Number(payload.code) !== 0 || !payload.data) { + if (Number(payload.code) === REFRESH_ERROR_CODE) { + clearStoredAppAuth(); + } + throw new Error(payload.message || '刷新登录状态失败'); + } + persistAppAuth(payload.data); + return payload.data; + }).finally(() => { + refreshPromise = null; + }); + return refreshPromise; +} +async function request(config) { + const response = await client.request(config); + const payload = response.data || {}; + if (Number(payload.code) === AUTH_ERROR_CODE && shouldAttachAuth(config) && !config._retriedAfterRefresh) { + await refreshAccessToken(); + return request({ + ...config, + _retriedAfterRefresh: true + }); + } + return response; +} +async function appServerRequest(config) { + return request(config); +} +async function appServerGet(url, config = {}) { + return request({ + ...config, + method: 'get', + url + }); +} +async function appServerPost(url, data = {}, config = {}) { + return request({ + ...config, + method: 'post', + url, + data + }); +} + +/***/ }), + +/***/ 4355: +/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ u: function() { return /* binding */ DocumentedHttpImClient; } +/* harmony export */ }); +/* harmony import */ var core_js_modules_es_array_includes_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4423); +/* harmony import */ var core_js_modules_es_array_includes_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_array_includes_js__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2169); +/* harmony import */ var _httpImClient__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(5099); +/* harmony import */ var axios__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(2387); +/* harmony import */ var _appServerClient__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(9311); + + + + + +function inferAccountType(account) { + return String(account || '').includes('@') ? 2 : 1; +} +function unwrapData(response) { + return response.data?.data ?? response.data?.result ?? null; +} +function unwrapMessage(response, fallback) { + return response.data?.message || response.data?.msg || fallback; +} +class DocumentedHttpImClient extends _httpImClient__WEBPACK_IMPORTED_MODULE_2__/* .HttpImClient */ .Y { + unsupported(feature) { + throw new Error(`当前对接文档未提供“${feature}”接口,已禁用旧本地代理链路`); + } + async loginWithPassword(account, password, type = inferAccountType(account)) { + const response = await (0,_appServerClient__WEBPACK_IMPORTED_MODULE_4__/* .appServerPost */ .Ln)(`${_config__WEBPACK_IMPORTED_MODULE_1__/* .appConfig */ .U.appServer}/auth/login_pwd`, { + type, + number: account, + password, + platform: _config__WEBPACK_IMPORTED_MODULE_1__/* .appConfig */ .U.platform, + clientId: this.getClientId() + }, { + skipAuth: true + }); + if (response.data?.code !== 0) { + throw new Error(unwrapMessage(response, '登录失败')); + } + (0,_appServerClient__WEBPACK_IMPORTED_MODULE_4__/* .persistAppAuth */ .eG)(unwrapData(response)); + return unwrapData(response); + } + async requestAuthCode(email) { + const response = await (0,_appServerClient__WEBPACK_IMPORTED_MODULE_4__/* .appServerPost */ .Ln)(`${_config__WEBPACK_IMPORTED_MODULE_1__/* .appConfig */ .U.appServer}/auth/sendEmailCode`, { + email + }, { + skipAuth: true + }); + if (response.data?.code !== 0) { + throw new Error(unwrapMessage(response, '验证码发送失败')); + } + return unwrapData(response); + } + async loginWithCode(account, code) { + void account; + void code; + throw new Error('接口文档未提供验证码登录接口,请使用密码登录'); + } + async register({ + type, + number, + code = '', + password, + invitationCode = '' + }) { + const response = await (0,_appServerClient__WEBPACK_IMPORTED_MODULE_4__/* .appServerPost */ .Ln)(`${_config__WEBPACK_IMPORTED_MODULE_1__/* .appConfig */ .U.appServer}/auth/register`, { + type, + number, + code, + password, + clientId: this.getClientId(), + platform: _config__WEBPACK_IMPORTED_MODULE_1__/* .appConfig */ .U.platform, + invitationCode + }, { + skipAuth: true + }); + if (response.data?.code !== 0) { + throw new Error(unwrapMessage(response, '注册失败')); + } + (0,_appServerClient__WEBPACK_IMPORTED_MODULE_4__/* .persistAppAuth */ .eG)(unwrapData(response)); + return unwrapData(response); + } + async createPcSession(userId = '') { + const response = await axios__WEBPACK_IMPORTED_MODULE_3__/* ["default"] */ .A.post(`${_config__WEBPACK_IMPORTED_MODULE_1__/* .appConfig */ .U.qrAppServer}/pc_session`, { + flag: 1, + device_name: 'Web IM', + userId, + clientId: this.getClientId(), + platform: _config__WEBPACK_IMPORTED_MODULE_1__/* .appConfig */ .U.platform + }, { + withCredentials: false + }); + if (response.data?.code !== 0) { + throw new Error(unwrapMessage(response, '二维码登录会话创建失败')); + } + return unwrapData(response) || {}; + } + async loginWithPcSession(token) { + const url = `${_config__WEBPACK_IMPORTED_MODULE_1__/* .appConfig */ .U.qrAppServer}/session_login/${encodeURIComponent(token)}`; + let response; + try { + response = await axios__WEBPACK_IMPORTED_MODULE_3__/* ["default"] */ .A.post(url, null, { + withCredentials: false + }); + } catch (postError) { + response = await axios__WEBPACK_IMPORTED_MODULE_3__/* ["default"] */ .A.get(url, { + withCredentials: false + }); + } + this.persistAppToken(url, response); + const payload = response.data || {}; + const result = payload.result ?? payload.data ?? null; + if (Number(payload.code) === 0 && result) { + (0,_appServerClient__WEBPACK_IMPORTED_MODULE_4__/* .persistAppAuth */ .eG)(result); + } + if ([0, 8, 9, 10, 13, 18, 20042].includes(Number(payload.code))) { + return { + ...payload, + data: result + }; + } + throw new Error(payload.message || '扫码登录失败'); + } + async changePassword({ + oldPassword, + newPassword + }) { + const response = await (0,_appServerClient__WEBPACK_IMPORTED_MODULE_4__/* .appServerPost */ .Ln)(_config__WEBPACK_IMPORTED_MODULE_1__/* .appConfig */ .U.changePasswordApi, { + oldPassword, + newPassword + }); + if (response.data?.code !== 0) { + throw new Error(unwrapMessage(response, '密码修改失败')); + } + return true; + } + async resetPasswordWithCode({ + mobile, + resetCode, + newPassword + }) { + void mobile; + void resetCode; + void newPassword; + throw new Error('接口文档未提供重置密码接口,请联系服务端确认流程'); + } + async logout() { + const auth = (0,_appServerClient__WEBPACK_IMPORTED_MODULE_4__/* .getStoredAppAuth */ .Pd)(); + if (!auth?.refreshToken) { + (0,_appServerClient__WEBPACK_IMPORTED_MODULE_4__/* .clearStoredAppAuth */ .u$)(); + return true; + } + try { + const response = await (0,_appServerClient__WEBPACK_IMPORTED_MODULE_4__/* .appServerPost */ .Ln)(_config__WEBPACK_IMPORTED_MODULE_1__/* .appConfig */ .U.logoutApi, { + refreshToken: auth.refreshToken + }); + if (response.data?.code !== 0) { + throw new Error(unwrapMessage(response, '退出登录失败')); + } + } finally { + (0,_appServerClient__WEBPACK_IMPORTED_MODULE_4__/* .clearStoredAppAuth */ .u$)(); + } + return true; + } + async fetchRemoteConversations() { + return []; + } + async fetchRemoteMessages() { + return []; + } + async getServerContactList() { + return []; + } + async getProfile() { + return null; + } + async sendTextMessage() { + this.unsupported('发送消息'); + } + async sendAttachmentMessage() { + this.unsupported('发送附件消息'); + } + async addContact() { + this.unsupported('添加联系人'); + } + async updateContactAlias() { + this.unsupported('联系人备注'); + } + async createGroup() { + this.unsupported('创建群组'); + } + async joinGroup() { + this.unsupported('加入群组'); + } + async broadcastMessage() { + this.unsupported('群发消息'); + } + async getGroupDetail() { + this.unsupported('群详情'); + } + async getGroupMembers() { + this.unsupported('群成员'); + } + async updateGroupAlias() { + this.unsupported('群备注'); + } + async leaveGroup() { + this.unsupported('退出群组'); + } + async dismissGroup() { + this.unsupported('解散群组'); + } + async transferGroup() { + this.unsupported('转让群组'); + } + async setGroupAdmins() { + this.unsupported('设置群管理员'); + } + async updateGroupProfile() { + this.unsupported('更新群资料'); + } + async kickGroupMembers() { + this.unsupported('移除群成员'); + } + async updateGroupMuteSetting() { + this.unsupported('群禁言设置'); + } + async updateProfile() { + this.unsupported('更新个人资料'); + } + async recallMessage() { + this.unsupported('撤回消息'); + } + async editMessage() { + this.unsupported('编辑消息'); + } + async getMessageDetail() { + this.unsupported('消息详情'); + } +} + +/***/ }), + +/***/ 5099: +/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ Y: function() { return /* binding */ HttpImClient; } +/* harmony export */ }); +/* unused harmony export imClient */ +/* harmony import */ var core_js_modules_es_array_includes_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4423); +/* harmony import */ var core_js_modules_es_array_includes_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_array_includes_js__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4114); +/* harmony import */ var core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var core_js_modules_es_iterator_constructor_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(8111); +/* harmony import */ var core_js_modules_es_iterator_constructor_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_constructor_js__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var core_js_modules_es_iterator_filter_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(2489); +/* harmony import */ var core_js_modules_es_iterator_filter_js__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_filter_js__WEBPACK_IMPORTED_MODULE_3__); +/* harmony import */ var core_js_modules_es_iterator_find_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(116); +/* harmony import */ var core_js_modules_es_iterator_find_js__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_find_js__WEBPACK_IMPORTED_MODULE_4__); +/* harmony import */ var core_js_modules_es_iterator_for_each_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(7588); +/* harmony import */ var core_js_modules_es_iterator_for_each_js__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_for_each_js__WEBPACK_IMPORTED_MODULE_5__); +/* harmony import */ var core_js_modules_es_iterator_map_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(1701); +/* harmony import */ var core_js_modules_es_iterator_map_js__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_map_js__WEBPACK_IMPORTED_MODULE_6__); +/* harmony import */ var core_js_modules_es_iterator_some_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(3579); +/* harmony import */ var core_js_modules_es_iterator_some_js__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_some_js__WEBPACK_IMPORTED_MODULE_7__); +/* harmony import */ var core_js_modules_es_map_get_or_insert_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(5367); +/* harmony import */ var core_js_modules_es_map_get_or_insert_js__WEBPACK_IMPORTED_MODULE_8___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_map_get_or_insert_js__WEBPACK_IMPORTED_MODULE_8__); +/* harmony import */ var core_js_modules_es_map_get_or_insert_computed_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(2731); +/* harmony import */ var core_js_modules_es_map_get_or_insert_computed_js__WEBPACK_IMPORTED_MODULE_9___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_map_get_or_insert_computed_js__WEBPACK_IMPORTED_MODULE_9__); +/* harmony import */ var core_js_modules_es_set_difference_v2_js__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(7642); +/* harmony import */ var core_js_modules_es_set_difference_v2_js__WEBPACK_IMPORTED_MODULE_10___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_set_difference_v2_js__WEBPACK_IMPORTED_MODULE_10__); +/* harmony import */ var core_js_modules_es_set_intersection_v2_js__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(8004); +/* harmony import */ var core_js_modules_es_set_intersection_v2_js__WEBPACK_IMPORTED_MODULE_11___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_set_intersection_v2_js__WEBPACK_IMPORTED_MODULE_11__); +/* harmony import */ var core_js_modules_es_set_is_disjoint_from_v2_js__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(3853); +/* harmony import */ var core_js_modules_es_set_is_disjoint_from_v2_js__WEBPACK_IMPORTED_MODULE_12___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_set_is_disjoint_from_v2_js__WEBPACK_IMPORTED_MODULE_12__); +/* harmony import */ var core_js_modules_es_set_is_subset_of_v2_js__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(5876); +/* harmony import */ var core_js_modules_es_set_is_subset_of_v2_js__WEBPACK_IMPORTED_MODULE_13___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_set_is_subset_of_v2_js__WEBPACK_IMPORTED_MODULE_13__); +/* harmony import */ var core_js_modules_es_set_is_superset_of_v2_js__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(2475); +/* harmony import */ var core_js_modules_es_set_is_superset_of_v2_js__WEBPACK_IMPORTED_MODULE_14___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_set_is_superset_of_v2_js__WEBPACK_IMPORTED_MODULE_14__); +/* harmony import */ var core_js_modules_es_set_symmetric_difference_v2_js__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(5024); +/* harmony import */ var core_js_modules_es_set_symmetric_difference_v2_js__WEBPACK_IMPORTED_MODULE_15___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_set_symmetric_difference_v2_js__WEBPACK_IMPORTED_MODULE_15__); +/* harmony import */ var core_js_modules_es_set_union_v2_js__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(1698); +/* harmony import */ var core_js_modules_es_set_union_v2_js__WEBPACK_IMPORTED_MODULE_16___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_set_union_v2_js__WEBPACK_IMPORTED_MODULE_16__); +/* harmony import */ var axios__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(2387); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(2169); +/* harmony import */ var _services_appServerClient__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(9311); + + + + + + + + + + + + + + + + + + + + +const LOCAL_MESSAGE_STORAGE_KEY = 'qmt-web-im-local-messages-v2'; +const LOCAL_HIDDEN_MESSAGE_STORAGE_KEY = 'qmt-web-im-hidden-messages-v1'; +const LOCAL_RECALLED_MESSAGE_STORAGE_KEY = 'qmt-web-im-recalled-messages-v1'; +function scopedStorageKey(baseKey, userId = '') { + const scope = `${_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.appServer || ''}:${userId || 'anonymous'}`; + return `${baseKey}:${encodeURIComponent(scope)}`; +} +function randomId() { + if (window.crypto && window.crypto.randomUUID) { + return window.crypto.randomUUID().replaceAll('-', ''); + } + return `${Date.now()}${Math.random().toString(16).slice(2)}`; +} +function readLocalMessages(userId) { + try { + return JSON.parse(localStorage.getItem(scopedStorageKey(LOCAL_MESSAGE_STORAGE_KEY, userId)) || '{}'); + } catch (error) { + return {}; + } +} +function readMessageSet(storageKey, userId) { + try { + return JSON.parse(localStorage.getItem(scopedStorageKey(storageKey, userId)) || '{}'); + } catch (error) { + return {}; + } +} +function writeMessageSet(storageKey, messagesByConversation, userId) { + localStorage.setItem(scopedStorageKey(storageKey, userId), JSON.stringify(messagesByConversation)); +} +function messageKeys(message) { + return [message?.id, message?.messageUid].filter(Boolean).map(value => String(value)); +} +function hasStoredMessage(storageKey, conversationId, message, userId) { + const stored = readMessageSet(storageKey, userId); + const ids = new Set(stored[conversationId] || []); + return messageKeys(message).some(id => ids.has(id)); +} +function storeMessageKeys(storageKey, conversationId, messages, userId) { + const stored = readMessageSet(storageKey, userId); + const ids = new Set(stored[conversationId] || []); + messages.forEach(message => { + messageKeys(message).forEach(id => ids.add(id)); + }); + stored[conversationId] = Array.from(ids); + writeMessageSet(storageKey, stored, userId); +} +function withRecalledState(message) { + return { + ...message, + status: 'recalled', + deliveryStatus: '', + recalled: true, + recallStatus: '已撤回' + }; +} +function applyLocalMessageState(conversationId, messages, userId) { + return messages.filter(message => !hasStoredMessage(LOCAL_HIDDEN_MESSAGE_STORAGE_KEY, conversationId, message, userId)).map(message => { + const isRecalled = Boolean(message.recalled) || message.status === 'recalled' || hasStoredMessage(LOCAL_RECALLED_MESSAGE_STORAGE_KEY, conversationId, message, userId); + if (!isRecalled) { + return message; + } + return withRecalledState(message); + }); +} +function writeLocalMessages(messagesByConversation, userId) { + localStorage.setItem(scopedStorageKey(LOCAL_MESSAGE_STORAGE_KEY, userId), JSON.stringify(messagesByConversation)); +} +function saveLocalMessage(message, userId) { + const messagesByConversation = readLocalMessages(userId); + messagesByConversation[message.conversationId] = messagesByConversation[message.conversationId] || []; + const index = messagesByConversation[message.conversationId].findIndex(item => item.id === message.id); + if (index >= 0) { + messagesByConversation[message.conversationId].splice(index, 1, message); + } else { + messagesByConversation[message.conversationId].push(message); + } + writeLocalMessages(messagesByConversation, userId); +} +function mergeSyncedMessages(localMessages, remoteMessages, conversationId, userId) { + const visibleLocalMessages = applyLocalMessageState(conversationId, localMessages, userId); + const localByKey = new Map(); + visibleLocalMessages.forEach(message => { + messageKeys(message).forEach(key => { + if (!localByKey.has(key)) { + localByKey.set(key, message); + } + }); + }); + const visibleRemoteMessages = applyLocalMessageState(conversationId, remoteMessages, userId).map(remoteMessage => { + if (!remoteMessage.recalled || String(remoteMessage.text || '').trim()) { + return remoteMessage; + } + const localMessage = messageKeys(remoteMessage).map(key => localByKey.get(key)).find(message => message?.text && message.text !== '你撤回了一条消息'); + return localMessage ? { + ...remoteMessage, + text: localMessage.text + } : remoteMessage; + }); + const remoteKeys = new Set(); + visibleRemoteMessages.forEach(message => { + messageKeys(message).forEach(key => remoteKeys.add(key)); + }); + const now = Date.now(); + function hasRemoteMatch(localMessage) { + if (localMessage.messageUid && remoteKeys.has(localMessage.messageUid)) { + return true; + } + return visibleRemoteMessages.some(remoteMessage => { + return remoteMessage.direction === localMessage.direction && remoteMessage.text === localMessage.text && Math.abs(Number(remoteMessage.timestamp || 0) - Number(localMessage.timestamp || 0)) < 15000; + }); + } + const pendingLocalMessages = visibleLocalMessages.filter(message => { + if (message.status === 'recalled' || message.recalled) { + return message.direction === 'out' && !hasRemoteMatch(message); + } + if (message.direction !== 'out') { + return false; + } + if (message.status === 'sent' && message.messageUid) { + return !hasRemoteMatch(message); + } + if (message.status === 'sent') { + return !hasRemoteMatch(message) && now - Number(message.timestamp || 0) < 30000; + } + return message.status === 'sending' || message.status === 'failed'; + }); + return [...visibleRemoteMessages, ...pendingLocalMessages].sort((a, b) => { + return Number(a.displayTimestamp || a.timestamp || 0) - Number(b.displayTimestamp || b.timestamp || 0); + }); +} +function getTitle(value, fallback) { + return value || fallback || ''; +} +function getUniqueEquipment(mobile, fallback) { + const allowedDevices = { + '18301902119': '67072d37062c4f31b1165073552933ba', + '18301902116': '1777479418113eeea8e236edce', + '18301902118': 'de89fb2f2a61480a8c381f6c16e28bd8', + '18301902117': 'd117f480faa64145b6162107b1b49617' + }; + return allowedDevices[mobile] || localStorage.getItem('uniqueEquipment') || fallback; +} +function canUseLocalLoginFallback(response) { + const message = response.data?.message || ''; + return false && 0; +} +class HttpImClient { + constructor() { + this.listeners = new Map(); + this.connected = false; + this.currentUser = null; + } + getClientId() { + let clientId = localStorage.getItem(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.clientIdStorageKey); + if (!clientId) { + clientId = randomId(); + localStorage.setItem(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.clientIdStorageKey, clientId); + } + return clientId; + } + restoreClientId(clientId) { + if (clientId) { + localStorage.setItem(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.clientIdStorageKey, clientId); + } + } + getSdkVersion() { + return 'HTTP'; + } + async ensureSdk() { + return true; + } + async loginWithPassword(mobile, password) { + const url = `${_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.appServer}/login_pwd`; + const deviceId = getUniqueEquipment(mobile, this.getClientId()); + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(url, { + mobile, + password, + platform: _config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.platform, + clientId: deviceId, + uniqueEquipment: deviceId + }, { + withCredentials: false + }); + this.persistAppToken(url, response); + if (response.data?.code !== 0) { + if (canUseLocalLoginFallback(response)) { + return { + userId: mobile, + token: `local-device-pending-${Date.now()}`, + displayName: mobile, + localOnly: true + }; + } + throw new Error(response.data?.message || '登录失败'); + } + return response.data.result; + } + async requestAuthCode(mobile) { + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(`${_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.appServer}/send_code`, { + mobile + }, { + withCredentials: false + }); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '验证码发送失败'); + } + return response.data.result; + } + async loginWithCode(mobile, code) { + const url = `${_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.appServer}/login`; + const deviceId = getUniqueEquipment(mobile, this.getClientId()); + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(url, { + mobile, + code, + platform: _config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.platform, + clientId: deviceId, + uniqueEquipment: deviceId + }, { + withCredentials: false + }); + this.persistAppToken(url, response); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '登录失败'); + } + return response.data.result; + } + async createPcSession(userId = '') { + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(`${_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.appServer}/pc_session`, { + flag: 1, + device_name: 'Web IM', + userId, + clientId: this.getClientId(), + platform: _config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.platform + }, { + withCredentials: false + }); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '二维码登录会话创建失败'); + } + return response.data.result || {}; + } + async loginWithPcSession(token) { + const url = `${_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.appServer}/session_login/${encodeURIComponent(token)}`; + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(url, null, { + withCredentials: false + }); + this.persistAppToken(url, response); + const data = response.data || {}; + if ([0, 8, 9, 10, 18].includes(Number(data.code))) { + return data; + } + throw new Error(data.message || '扫码登录失败'); + } + async registerWithPhone(mobile, code) { + const url = `${_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.appServer}/register`; + const deviceId = getUniqueEquipment(mobile, this.getClientId()); + try { + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(url, { + mobile, + code, + platform: _config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.platform, + clientId: deviceId, + uniqueEquipment: deviceId + }, { + withCredentials: false + }); + this.persistAppToken(url, response); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '注册失败'); + } + return response.data.result; + } catch (error) { + const status = error.response?.status; + if (status === 404 || status === 405) { + return this.loginWithCode(mobile, code); + } + throw error; + } + } + connect(userId, token) { + this.currentUser = { + userId, + token + }; + this.connected = true; + this.emit('connectionStatusChanged', { + connected: true, + statusText: '已连接' + }); + this.refreshConversationList(); + } + disconnect() { + this.connected = false; + this.currentUser = null; + this.emit('connectionStatusChanged', { + connected: false, + statusText: '未连接' + }); + } + getConversationList() { + return []; + } + refreshConversationList() { + const conversations = this.getConversationList(); + if (conversations.length) { + this.emit('conversationsChanged', conversations); + } + if (this.currentUser?.userId) { + this.fetchRemoteConversations().then(remoteConversations => { + this.emit('conversationsChanged', remoteConversations); + }).catch(() => {}); + } + return conversations; + } + async fetchRemoteConversations(limit = 50) { + if (!this.currentUser?.userId) { + return []; + } + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.messagesApi, { + userId: this.currentUser.userId, + limit + }, { + withCredentials: false + }); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '会话同步失败'); + } + return (response.data?.result?.conversations || []).map(conversation => ({ + ...conversation, + updatedAt: Number(conversation.updatedAt || Date.now()), + avatarText: conversation.avatarText || String(conversation.title || conversation.target || '?').slice(0, 1).toUpperCase(), + raw: conversation.raw || { + type: 'single', + target: conversation.target, + line: 0 + } + })); + } + getMessages(conversation) { + if (!conversation?.id) { + return []; + } + const userId = this.currentUser?.userId || ''; + return applyLocalMessageState(conversation.id, readLocalMessages(userId)[conversation.id] || [], userId); + } + async fetchRemoteMessages(conversation, limit = 50, options = {}) { + if (!conversation?.id || !this.currentUser?.userId) { + return []; + } + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.messagesApi, { + userId: this.currentUser.userId, + target: conversation.target || conversation.raw?.target || conversation.id.replace(/^single-/, ''), + limit, + conversationType: conversation.type || conversation.raw?.type || 0, + markRead: Boolean(options.markRead) + }, { + withCredentials: false + }); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '消息同步失败'); + } + const messages = (response.data?.result?.messages || []).map(message => ({ + ...message, + displayTimestamp: message.displayTimestamp || message.timestamp, + conversationId: conversation.id + })); + const userId = this.currentUser?.userId || ''; + const messagesByConversation = readLocalMessages(userId); + messagesByConversation[conversation.id] = mergeSyncedMessages(messagesByConversation[conversation.id] || [], messages, conversation.id, userId); + writeLocalMessages(messagesByConversation, userId); + return messagesByConversation[conversation.id]; + } + hideMessages(conversation, messages) { + const items = Array.isArray(messages) ? messages : [messages]; + if (!conversation?.id || !items.length) { + return; + } + const userId = this.currentUser?.userId || ''; + storeMessageKeys(LOCAL_HIDDEN_MESSAGE_STORAGE_KEY, conversation.id, items, userId); + const messagesByConversation = readLocalMessages(userId); + messagesByConversation[conversation.id] = applyLocalMessageState(conversation.id, messagesByConversation[conversation.id] || [], userId); + writeLocalMessages(messagesByConversation, userId); + } + markMessagesRecalled(conversation, messages) { + const items = Array.isArray(messages) ? messages : [messages]; + if (!conversation?.id || !items.length) { + return; + } + const userId = this.currentUser?.userId || ''; + storeMessageKeys(LOCAL_RECALLED_MESSAGE_STORAGE_KEY, conversation.id, items, userId); + const messagesByConversation = readLocalMessages(userId); + messagesByConversation[conversation.id] = applyLocalMessageState(conversation.id, messagesByConversation[conversation.id] || [], userId); + writeLocalMessages(messagesByConversation, userId); + } + async recallMessage(conversation, message) { + const messageUid = String(message?.messageUid || message?.id || '').trim(); + if (!/^\d+$/.test(messageUid)) { + throw new Error('缺少可撤回的消息ID'); + } + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.messageActionApi, { + action: 'recall', + userId: this.currentUser?.userId || message.from || '', + messageUid + }, { + withCredentials: false + }); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '撤回失败'); + } + this.markMessagesRecalled(conversation, [message]); + return response.data.result; + } + async editMessage(conversation, message, text) { + const messageUid = String(message?.messageUid || message?.id || '').trim(); + if (!/^\d+$/.test(messageUid)) { + throw new Error('缺少可编辑的消息ID'); + } + const nextText = String(text || '').trim(); + if (!nextText) { + throw new Error('编辑内容不能为空'); + } + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.messageActionApi, { + action: 'edit', + userId: this.currentUser?.userId || message.from || '', + messageUid, + text: nextText + }, { + withCredentials: false + }); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '编辑失败'); + } + const result = response.data?.result?.message || {}; + const editedMessage = { + ...message, + text: result.text || nextText, + status: 'delivered', + deliveryStatus: 'delivered', + editedAt: Number(result.editedAt || Date.now()), + activityTimestamp: Number(result.editedAt || Date.now()) + }; + saveLocalMessage(editedMessage, this.currentUser?.userId || ''); + this.emit('messageStatusChanged', editedMessage); + return editedMessage; + } + async getMessageDetail(conversation, message) { + const messageUid = String(message?.messageUid || message?.id || '').trim(); + if (!messageUid) { + throw new Error('缺少消息ID'); + } + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.messageDetailApi, { + userId: this.currentUser?.userId || '', + target: conversation?.target || conversation?.raw?.target || conversation?.id?.replace(/^single-/, '') || '', + conversationType: conversation?.type || conversation?.raw?.type || 'single', + messageUid + }, { + withCredentials: false + }); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '消息详情加载失败'); + } + return response.data?.result?.detail || null; + } + async sendTextMessage(conversation, text, initialMessage = null) { + const senderRole = conversation?.role || conversation?.raw?.role || ''; + const message = initialMessage || { + id: randomId(), + conversationId: conversation.id, + from: this.currentUser?.userId || 'me', + senderRole, + direction: 'out', + type: 'text', + text, + status: 'sending', + deliveryStatus: 'sending', + timestamp: Date.now() + }; + saveLocalMessage(message, this.currentUser?.userId || ''); + if (!initialMessage) { + this.emit('messageSent', message); + } + try { + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.sendMessageApi, { + sender: this.currentUser?.userId || '', + target: conversation.target || conversation.raw?.target || conversation.id.replace(/^single-/, ''), + conversationType: conversation.type || conversation.raw?.type || 0, + text + }, { + withCredentials: false + }); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '发送失败'); + } + const sentMessage = { + ...message, + status: 'sent', + deliveryStatus: 'sent', + messageUid: response.data?.result?.messageUid ? String(response.data.result.messageUid) : '', + senderRole: response.data?.result?.senderRole || message.senderRole || '', + serverTimestamp: Number(response.data?.result?.timestamp || 0) || null, + timestamp: message.timestamp, + displayTimestamp: message.displayTimestamp || message.timestamp + }; + saveLocalMessage(sentMessage, this.currentUser?.userId || ''); + this.emit('messageStatusChanged', sentMessage); + return sentMessage; + } catch (error) { + const failedMessage = { + ...message, + status: 'failed', + deliveryStatus: '', + error: error.response?.data?.message || error.message || '发送失败' + }; + saveLocalMessage(failedMessage, this.currentUser?.userId || ''); + this.emit('messageStatusChanged', failedMessage); + return failedMessage; + } + } + async sendAttachmentMessage(conversation, { + type, + file, + text = '' + }, initialMessage = null) { + const senderRole = conversation?.role || conversation?.raw?.role || ''; + const message = initialMessage || { + id: randomId(), + conversationId: conversation.id, + from: this.currentUser?.userId || 'me', + senderRole, + direction: 'out', + type, + text, + attachment: { + type, + name: file?.name || (type === 'image' ? '图片' : '文件'), + mime: file?.type || '', + size: file?.size || 0, + url: type === 'image' && file ? URL.createObjectURL(file) : '' + }, + status: 'sending', + deliveryStatus: 'sending', + timestamp: Date.now() + }; + saveLocalMessage(message, this.currentUser?.userId || ''); + if (!initialMessage) { + this.emit('messageSent', message); + } + try { + const formData = new FormData(); + formData.append('sender', this.currentUser?.userId || ''); + formData.append('target', conversation.target || conversation.raw?.target || conversation.id.replace(/^single-/, '')); + formData.append('conversationType', conversation.type || conversation.raw?.type || 0); + formData.append('messageType', type); + formData.append('text', text || ''); + formData.append('attachment', file); + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.sendMessageApi, formData, { + withCredentials: false + }); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '发送失败'); + } + const sentMessage = { + ...message, + status: 'sent', + deliveryStatus: 'sent', + messageUid: response.data?.result?.messageUid ? String(response.data.result.messageUid) : '', + senderRole: response.data?.result?.senderRole || message.senderRole || '', + attachment: response.data?.result?.attachment || message.attachment, + serverTimestamp: Number(response.data?.result?.timestamp || 0) || null, + timestamp: message.timestamp, + displayTimestamp: message.displayTimestamp || message.timestamp + }; + saveLocalMessage(sentMessage, this.currentUser?.userId || ''); + this.emit('messageStatusChanged', sentMessage); + return sentMessage; + } catch (error) { + const failedMessage = { + ...message, + status: 'failed', + deliveryStatus: '', + error: error.response?.data?.message || error.message || '发送失败' + }; + saveLocalMessage(failedMessage, this.currentUser?.userId || ''); + this.emit('messageStatusChanged', failedMessage); + return failedMessage; + } + } + getContactList() { + return []; + } + async getServerContactList(userId) { + if (!userId) { + return []; + } + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.contactsApi, { + userId + }, { + withCredentials: false, + headers: this.getAppAuthHeaders(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.contactsApi) + }); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '联系人加载失败'); + } + return (response.data?.result?.contacts || []).map(item => { + return this.mapContact(item.uid, { + alias: item.alias + }, { + uid: item.uid, + displayName: item.displayName, + name: item.name, + mobile: item.mobile, + portrait: item.portrait, + address: item.address, + friendAlias: item.alias + }); + }).sort((a, b) => a.title.localeCompare(b.title, 'zh-CN')); + } + async addContact(userId, keyword, reason = '') { + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.addContactApi, { + userId, + keyword, + reason + }, { + withCredentials: false + }); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '添加联系人失败'); + } + const item = response.data?.result?.contact; + if (!item?.uid) { + return null; + } + return this.mapContact(item.uid, { + alias: item.alias + }, { + uid: item.uid, + displayName: item.displayName, + name: item.name, + mobile: item.mobile, + portrait: item.portrait, + address: item.address, + friendAlias: item.alias + }); + } + normalizeConversation(conversation) { + const title = getTitle(conversation?.title, conversation?.target || ''); + return { + ...conversation, + id: conversation?.id || `${conversation?.type === 'single' ? 'single' : 'group'}-${conversation?.target}`, + target: conversation?.target || conversation?.raw?.target || '', + title, + subtitle: conversation?.subtitle || '', + unread: Number(conversation?.unread || 0), + portrait: conversation?.portrait || '', + avatarText: conversation?.avatarText || title.slice(0, 1).toUpperCase(), + updatedAt: Number(conversation?.updatedAt || Date.now()), + raw: conversation?.raw || { + type: conversation?.type === 'single' ? 'single' : 'group', + target: conversation?.target || '', + line: 0 + } + }; + } + async createGroup(userId, { + name, + memberIds = [], + announcement = '', + welcomeText = '', + channel = false + }) { + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.conversationActionsApi, { + action: channel ? 'create_channel' : 'create_group', + userId, + name, + memberIds, + announcement, + welcomeText + }, { + withCredentials: false + }); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || (channel ? '创建公告群失败' : '创建群组失败')); + } + return this.normalizeConversation(response.data?.result?.conversation || {}); + } + async joinGroup(userId, groupKey) { + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.conversationActionsApi, { + action: 'join_group', + userId, + groupKey + }, { + withCredentials: false + }); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '加入群组失败'); + } + return this.normalizeConversation(response.data?.result?.conversation || {}); + } + async broadcastMessage(userId, targetIds, text) { + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.conversationActionsApi, { + action: 'broadcast', + userId, + targetIds, + text + }, { + withCredentials: false + }); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '群发失败'); + } + return response.data?.result || {}; + } + async getGroupDetail(userId, groupId) { + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.conversationActionsApi, { + action: 'group_detail', + userId, + groupId + }, { + withCredentials: false + }); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '群资料加载失败'); + } + return response.data?.result?.group || null; + } + async getGroupMembers(userId, groupId) { + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.conversationActionsApi, { + action: 'group_members', + userId, + groupId + }, { + withCredentials: false + }); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '群员加载失败'); + } + return response.data?.result?.members || []; + } + async updateGroupAlias(userId, groupId, alias) { + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.conversationActionsApi, { + action: 'update_group_alias', + userId, + groupId, + alias + }, { + withCredentials: false + }); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '本群昵称保存失败'); + } + return response.data?.result?.alias || ''; + } + async leaveGroup(userId, groupId) { + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.conversationActionsApi, { + action: 'leave_group', + userId, + groupId + }, { + withCredentials: false + }); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '退出群组失败'); + } + return response.data?.result || {}; + } + async dismissGroup(userId, groupId) { + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.conversationActionsApi, { + action: 'dismiss_group', + userId, + groupId + }, { + withCredentials: false + }); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '解散群组失败'); + } + return response.data?.result || {}; + } + async transferGroup(userId, groupId, targetUserId) { + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.conversationActionsApi, { + action: 'transfer_group', + userId, + groupId, + targetUserId + }, { + withCredentials: false + }); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '转让群组失败'); + } + return response.data?.result || {}; + } + async setGroupAdmins(userId, groupId, adminIds) { + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.conversationActionsApi, { + action: 'set_group_admins', + userId, + groupId, + adminIds + }, { + withCredentials: false + }); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '管理员设置失败'); + } + return response.data?.result || {}; + } + async updateGroupProfile(userId, groupId, { + name, + announcement, + welcomeText + }) { + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.conversationActionsApi, { + action: 'update_group_profile', + userId, + groupId, + name, + announcement, + welcomeText + }, { + withCredentials: false + }); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '群资料保存失败'); + } + return response.data?.result || {}; + } + async kickGroupMembers(userId, groupId, targetIds) { + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.conversationActionsApi, { + action: 'kick_group_members', + userId, + groupId, + targetIds + }, { + withCredentials: false + }); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '踢人失败'); + } + return response.data?.result || {}; + } + async updateGroupMuteSetting(userId, groupId, setting) { + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.conversationActionsApi, { + action: 'update_mute_setting', + userId, + groupId, + ...setting + }, { + withCredentials: false + }); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '禁言设置保存失败'); + } + return response.data?.result?.muteSetting || null; + } + async updateContactAlias(userId, targetId, alias) { + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.updateAliasApi, { + userId, + targetId, + alias + }, { + withCredentials: false, + headers: this.getAppAuthHeaders(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.updateAliasApi) + }); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '备注名修改失败'); + } + return response.data?.result?.contact?.alias || ''; + } + async getProfile(userId) { + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.profileApi, { + action: 'get', + userId + }, { + withCredentials: false + }); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '用户资料加载失败'); + } + return response.data?.result?.profile || null; + } + async updateProfile(userId, { + displayName, + portraitFile + }) { + const formData = new FormData(); + formData.append('action', 'update'); + formData.append('userId', userId); + formData.append('displayName', displayName); + if (portraitFile) { + formData.append('portraitFile', portraitFile); + } + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.profileApi, formData, { + withCredentials: false, + headers: this.getAppAuthHeaders(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.appServer) + }); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '用户资料保存失败'); + } + return response.data?.result?.profile || null; + } + async changePassword({ + oldPassword, + newPassword + }) { + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.changePasswordApi, { + oldPassword, + newPassword + }, { + withCredentials: false, + headers: this.getAppAuthHeaders(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.changePasswordApi) + }); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || response.data?.msg || '密码修改失败'); + } + return true; + } + async resetPasswordWithCode({ + mobile, + resetCode, + newPassword + }) { + const response = await axios__WEBPACK_IMPORTED_MODULE_17__/* ["default"] */ .A.post(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.resetPasswordApi, { + mobile, + resetCode, + newPassword + }, { + withCredentials: false + }); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || response.data?.msg || '密码设置失败'); + } + return true; + } + refreshContactList() { + const contacts = this.getContactList(); + this.emit('contactsChanged', contacts); + return contacts; + } + createSingleConversation(userId, userInfo = null) { + const title = getTitle(userInfo?.title || userInfo?.displayName || userInfo?.name, userId); + return { + id: `single-${userId}`, + type: 'single', + target: userId, + title, + subtitle: '', + unread: 0, + portrait: userInfo?.portrait || '', + avatarText: title.slice(0, 1).toUpperCase(), + updatedAt: Date.now(), + raw: { + type: 'single', + target: userId, + line: 0 + } + }; + } + mapContact(userId, friend, userInfo) { + const title = getTitle(userInfo?.friendAlias || friend?.alias || userInfo?.displayName || userInfo?.name, userId); + return { + id: userId, + uid: userId, + title, + subtitle: userInfo?.mobile || userInfo?.name || userId, + portrait: userInfo?.portrait || '', + address: userInfo?.address || '', + avatarText: title.slice(0, 1).toUpperCase(), + alias: friend?.alias || userInfo?.friendAlias || '', + rawFriend: friend, + rawUser: userInfo + }; + } + on(eventName, handler) { + if (!this.listeners.has(eventName)) { + this.listeners.set(eventName, new Set()); + } + this.listeners.get(eventName).add(handler); + return () => this.off(eventName, handler); + } + off(eventName, handler) { + this.listeners.get(eventName)?.delete(handler); + } + emit(eventName, payload) { + this.listeners.get(eventName)?.forEach(handler => handler(payload)); + } + persistAppToken(url, response) { + const appToken = response.headers?.authtoken || response.headers?.authToken; + if (appToken) { + localStorage.setItem(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.authTokenStoragePrefix + this.getUrlHost(url), appToken); + } + } + getAppAuthHeaders(url) { + const host = this.getUrlHost(url); + if (!host) { + return {}; + } + const token = localStorage.getItem(_config__WEBPACK_IMPORTED_MODULE_18__/* .appConfig */ .U.authTokenStoragePrefix + host); + return token ? { + authToken: token + } : {}; + } + getUrlHost(url) { + try { + return new URL(url, window.location.origin).host; + } catch (error) { + return ''; + } + } +} +const imClient = new HttpImClient(); + +/***/ }), + +/***/ 5051: +/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +/* unused harmony export WildfireImClient */ +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2169); +/* harmony import */ var _httpImClient__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(5099); +/* harmony import */ var _longConnectionImClient__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(3909); +/* harmony import */ var _documentedHttpImClient__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(4355); + + + + +class UnsupportedWildfireImClient { + constructor() { + throw new Error('当前本地社区版 Web 客户端未启用 WildFire Web SDK'); + } +} +const imClient = _config__WEBPACK_IMPORTED_MODULE_0__/* .appConfig */ .U.sdkMode === 'wildfire' ? new UnsupportedWildfireImClient() : _config__WEBPACK_IMPORTED_MODULE_0__/* .appConfig */ .U.sdkMode === 'custom-ws' || _config__WEBPACK_IMPORTED_MODULE_0__/* .appConfig */ .U.sdkMode === 'long' ? new _longConnectionImClient__WEBPACK_IMPORTED_MODULE_2__/* .LongConnectionImClient */ .E() : new _documentedHttpImClient__WEBPACK_IMPORTED_MODULE_3__/* .DocumentedHttpImClient */ .u(); + +/* harmony export */ __webpack_require__.d(__webpack_exports__, [ +/* harmony export */ "wD", 0, /* binding */ imClient +/* harmony export */ ]); + + +/***/ }), + +/***/ 3909: +/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ E: function() { return /* binding */ LongConnectionImClient; } +/* harmony export */ }); +/* harmony import */ var core_js_modules_es_iterator_constructor_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(8111); +/* harmony import */ var core_js_modules_es_iterator_constructor_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_constructor_js__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var core_js_modules_es_iterator_filter_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2489); +/* harmony import */ var core_js_modules_es_iterator_filter_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_filter_js__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var core_js_modules_es_iterator_for_each_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(7588); +/* harmony import */ var core_js_modules_es_iterator_for_each_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_for_each_js__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var core_js_modules_es_iterator_map_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(1701); +/* harmony import */ var core_js_modules_es_iterator_map_js__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_map_js__WEBPACK_IMPORTED_MODULE_3__); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(2169); +/* harmony import */ var _httpImClient__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(5099); + + + + + + +const HEARTBEAT_INTERVAL_MS = 25000; +const RECONNECT_MIN_MS = 1000; +const RECONNECT_MAX_MS = 15000; +function buildWsUrl() { + if (_config__WEBPACK_IMPORTED_MODULE_4__/* .appConfig */ .U.wsUrl) { + return _config__WEBPACK_IMPORTED_MODULE_4__/* .appConfig */ .U.wsUrl; + } + const protocol = _config__WEBPACK_IMPORTED_MODULE_4__/* .appConfig */ .U.useWss ? 'wss' : 'ws'; + const host = _config__WEBPACK_IMPORTED_MODULE_4__/* .appConfig */ .U.imServerHost || window.location.hostname; + const port = _config__WEBPACK_IMPORTED_MODULE_4__/* .appConfig */ .U.imRoutePort ? `:${_config__WEBPACK_IMPORTED_MODULE_4__/* .appConfig */ .U.imRoutePort}` : ''; + const path = _config__WEBPACK_IMPORTED_MODULE_4__/* .appConfig */ .U.wsPath?.startsWith('/') ? _config__WEBPACK_IMPORTED_MODULE_4__/* .appConfig */ .U.wsPath : `/${_config__WEBPACK_IMPORTED_MODULE_4__/* .appConfig */ .U.wsPath || 'ws'}`; + return `${protocol}://${host}${port}${path}`; +} +function normalizeConversationId(payload) { + const target = payload.target || payload.to || payload.receiver || payload.conversationTarget || payload.from || payload.sender; + return payload.conversationId || (target ? `single-${target}` : ''); +} +function normalizeIncomingMessage(payload, currentUserId) { + const item = payload.message || payload; + const from = item.from || item.sender || item.fromUser || ''; + const target = item.target || item.to || item.receiver || ''; + const direction = from && String(from) === String(currentUserId) ? 'out' : 'in'; + const conversationId = item.conversationId || normalizeConversationId({ + ...item, + target: direction === 'out' ? target : from + }); + if (!conversationId) { + return null; + } + const status = item.status || item.deliveryStatus || 'sent'; + return { + id: item.id || item.messageId || item.messageUid || `${Date.now()}${Math.random().toString(16).slice(2)}`, + messageUid: item.messageUid || item.uid || item.messageId || '', + conversationId, + from, + target, + direction, + type: item.type || 'text', + text: item.text || item.content || item.searchableContent || '', + status, + deliveryStatus: item.deliveryStatus || (direction === 'out' ? status : ''), + timestamp: Number(item.timestamp || item.serverTimestamp || Date.now()), + displayTimestamp: Number(item.displayTimestamp || item.timestamp || item.serverTimestamp || Date.now()), + raw: item + }; +} +class LongConnectionImClient extends _httpImClient__WEBPACK_IMPORTED_MODULE_5__/* .HttpImClient */ .Y { + constructor() { + super(); + this.socket = null; + this.wsUrl = buildWsUrl(); + this.heartbeatTimer = null; + this.reconnectTimer = null; + this.reconnectAttempts = 0; + this.manualClose = false; + } + getSdkVersion() { + return 'JC-LC/0.1'; + } + shouldUseMessagePolling() { + return !this.connected; + } + async ensureSdk() { + if (typeof WebSocket === 'undefined') { + throw new Error('当前浏览器不支持 WebSocket 长连接'); + } + return true; + } + connect(userId, token) { + this.currentUser = { + userId, + token + }; + this.manualClose = false; + this.openSocket(); + } + disconnect() { + this.manualClose = true; + this.clearReconnectTimer(); + this.clearHeartbeatTimer(); + if (this.socket) { + this.socket.close(); + this.socket = null; + } + super.disconnect(); + } + openSocket() { + if (this.socket?.readyState === WebSocket.OPEN) { + this.sendAuthFrame(); + return; + } + if (this.socket?.readyState === WebSocket.CONNECTING) { + return; + } + this.emit('connectionStatusChanged', { + connected: false, + statusText: '长连接连接中' + }); + try { + this.socket = new WebSocket(this.wsUrl); + this.socket.binaryType = 'arraybuffer'; + } catch (error) { + this.emit('connectionStatusChanged', { + connected: false, + statusText: '长连接创建失败,使用HTTP补偿' + }); + this.scheduleReconnect(); + return; + } + this.socket.onopen = () => { + this.connected = true; + this.reconnectAttempts = 0; + this.emit('connectionStatusChanged', { + connected: true, + statusText: '长连接已连接' + }); + this.sendAuthFrame(); + this.startHeartbeat(); + this.refreshConversationList(); + }; + this.socket.onmessage = event => { + this.handleFrame(event.data); + }; + this.socket.onerror = () => { + this.emit('connectionStatusChanged', { + connected: false, + statusText: '长连接异常,使用HTTP补偿' + }); + }; + this.socket.onclose = () => { + this.connected = false; + this.clearHeartbeatTimer(); + this.socket = null; + if (this.manualClose) { + return; + } + this.emit('connectionStatusChanged', { + connected: false, + statusText: '长连接重连中' + }); + this.scheduleReconnect(); + }; + } + sendAuthFrame() { + this.sendFrame({ + type: 'auth', + userId: this.currentUser?.userId || '', + token: this.currentUser?.token || '', + clientId: this.getClientId(), + platform: _config__WEBPACK_IMPORTED_MODULE_4__/* .appConfig */ .U.platform, + timestamp: Date.now() + }); + } + sendFrame(frame) { + if (this.socket?.readyState !== WebSocket.OPEN) { + return false; + } + this.socket.send(JSON.stringify(frame)); + return true; + } + handleFrame(rawData) { + if (rawData instanceof ArrayBuffer) { + this.emit('rawFrameReceived', rawData); + return; + } + let payload; + try { + payload = typeof rawData === 'string' ? JSON.parse(rawData) : rawData; + } catch (error) { + return; + } + const type = payload.type || payload.event || payload.cmd || ''; + if (type === 'pong' || type === 'auth_ok' || type === 'connected') { + return; + } + if (type === 'contactsChanged') { + this.emit('contactsChanged', payload.contacts || []); + return; + } + if (type === 'conversationsChanged') { + this.emit('conversationsChanged', payload.conversations || []); + return; + } + if (type === 'messages' && Array.isArray(payload.messages)) { + payload.messages.map(message => normalizeIncomingMessage(message, this.currentUser?.userId)).filter(Boolean).forEach(message => this.emit('messageReceived', message)); + return; + } + const message = normalizeIncomingMessage(payload, this.currentUser?.userId); + if (message) { + this.emit(message.direction === 'out' ? 'messageStatusChanged' : 'messageReceived', message); + } + } + startHeartbeat() { + this.clearHeartbeatTimer(); + this.heartbeatTimer = window.setInterval(() => { + this.sendFrame({ + type: 'ping', + timestamp: Date.now() + }); + }, HEARTBEAT_INTERVAL_MS); + } + clearHeartbeatTimer() { + if (this.heartbeatTimer) { + window.clearInterval(this.heartbeatTimer); + this.heartbeatTimer = null; + } + } + scheduleReconnect() { + this.clearReconnectTimer(); + const delay = Math.min(RECONNECT_MAX_MS, RECONNECT_MIN_MS * 2 ** this.reconnectAttempts); + this.reconnectAttempts += 1; + this.reconnectTimer = window.setTimeout(() => { + this.openSocket(); + }, delay); + } + clearReconnectTimer() { + if (this.reconnectTimer) { + window.clearTimeout(this.reconnectTimer); + this.reconnectTimer = null; + } + } +} + +/***/ }), + +/***/ 926: +/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +/* harmony import */ var core_js_modules_es_array_includes_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4423); +/* harmony import */ var core_js_modules_es_array_includes_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_array_includes_js__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var pinia__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2261); +/* harmony import */ var _services_imClient__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(5051); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(2169); + + + + +const STORAGE_KEY = 'qmt-web-im-session'; +function normalizeStoredPortrait(portrait) { + return portrait || ''; +} +function nextProfilePortrait(profilePortrait, currentPortrait) { + return normalizeStoredPortrait(profilePortrait) || normalizeStoredPortrait(currentPortrait); +} +function inferAccountType(account) { + return String(account || '').includes('@') ? 2 : 1; +} +const useSessionStore = (0,pinia__WEBPACK_IMPORTED_MODULE_1__/* .defineStore */ .nY)('session', { + state: () => ({ + isAuthenticated: false, + connecting: false, + connected: false, + connectionStatusText: '未连接', + userId: '', + token: '', + displayName: '', + portrait: '', + error: '', + sdkVersion: '', + eventsBound: false + }), + actions: { + restore() { + const raw = localStorage.getItem(STORAGE_KEY); + if (!raw) { + return; + } + try { + const session = JSON.parse(raw); + if (session.appServer !== _config__WEBPACK_IMPORTED_MODULE_3__/* .appConfig */ .U.appServer) { + localStorage.removeItem(STORAGE_KEY); + return; + } + if (session.userId && session.token) { + if (!session.clientId) { + localStorage.removeItem(STORAGE_KEY); + return; + } + this.userId = session.userId; + this.token = session.token; + this.displayName = session.displayName || session.userId; + this.portrait = normalizeStoredPortrait(session.portrait); + this.isAuthenticated = true; + _services_imClient__WEBPACK_IMPORTED_MODULE_2__/* .imClient */ .wD.restoreClientId(session.clientId); + this.connect(); + this.refreshProfile(); + } + } catch (error) { + localStorage.removeItem(STORAGE_KEY); + } + }, + async loginWithPassword(account, password, type = inferAccountType(account)) { + this.error = ''; + this.connecting = true; + try { + const result = await _services_imClient__WEBPACK_IMPORTED_MODULE_2__/* .imClient */ .wD.loginWithPassword(account, password, type); + this.applyLoginResult(result); + await this.connect(); + } catch (error) { + this.error = error.message || '登录失败'; + } finally { + this.connecting = false; + } + }, + async loginWithCode(account, code) { + this.error = ''; + this.connecting = true; + try { + const result = await _services_imClient__WEBPACK_IMPORTED_MODULE_2__/* .imClient */ .wD.loginWithCode(account, code); + this.applyLoginResult(result); + await this.connect(); + } catch (error) { + this.error = error.message || '登录失败'; + } finally { + this.connecting = false; + } + }, + async completePcSessionLogin(result) { + this.error = ''; + this.connecting = true; + try { + this.applyLoginResult(result); + await this.connect(); + } catch (error) { + this.error = error.message || '扫码登录失败'; + } finally { + this.connecting = false; + } + }, + async register(payload) { + this.error = ''; + this.connecting = true; + try { + const result = await _services_imClient__WEBPACK_IMPORTED_MODULE_2__/* .imClient */ .wD.register(payload); + this.applyLoginResult(result); + await this.connect(); + return result; + } catch (error) { + this.error = error.message || '注册失败'; + return null; + } finally { + this.connecting = false; + } + }, + applyLoginResult(result) { + this.userId = result.userId || result.uid || result.id || ''; + this.token = result.token || result.imToken || ''; + this.displayName = result.displayName || result.userName || result.name || this.userId; + this.portrait = normalizeStoredPortrait(result.portrait); + this.isAuthenticated = Boolean(this.userId && this.token); + this.persistSession(); + }, + persistSession() { + localStorage.setItem(STORAGE_KEY, JSON.stringify({ + userId: this.userId, + token: this.token, + displayName: this.displayName, + portrait: this.portrait, + clientId: _services_imClient__WEBPACK_IMPORTED_MODULE_2__/* .imClient */ .wD.getClientId(), + appServer: _config__WEBPACK_IMPORTED_MODULE_3__/* .appConfig */ .U.appServer + })); + }, + applyProfile(profile) { + if (!profile) { + return; + } + this.userId = profile.userId || profile.uid || this.userId; + this.displayName = profile.displayName || profile.name || this.displayName || this.userId; + this.portrait = nextProfilePortrait(profile.portrait, this.portrait); + this.persistSession(); + }, + async refreshProfile() { + if (!this.userId) { + return null; + } + try { + const profile = await _services_imClient__WEBPACK_IMPORTED_MODULE_2__/* .imClient */ .wD.getProfile(this.userId); + this.applyProfile(profile); + return profile; + } catch (error) { + return null; + } + }, + async saveProfile({ + displayName, + portraitFile + }) { + if (!this.userId) { + throw new Error('请先登录'); + } + const profile = await _services_imClient__WEBPACK_IMPORTED_MODULE_2__/* .imClient */ .wD.updateProfile(this.userId, { + displayName, + portraitFile + }); + this.applyProfile(profile); + return profile; + }, + async changePassword({ + oldPassword, + newPassword + }) { + if (!this.userId) { + throw new Error('请先登录'); + } + await _services_imClient__WEBPACK_IMPORTED_MODULE_2__/* .imClient */ .wD.changePassword({ + oldPassword, + newPassword + }); + await this.logout(); + return true; + }, + async resetPasswordWithCode({ + mobile, + resetCode, + newPassword + }) { + await _services_imClient__WEBPACK_IMPORTED_MODULE_2__/* .imClient */ .wD.resetPasswordWithCode({ + mobile, + resetCode, + newPassword + }); + return true; + }, + async connect() { + if (!this.userId || !this.token) { + return; + } + await _services_imClient__WEBPACK_IMPORTED_MODULE_2__/* .imClient */ .wD.ensureSdk(); + this.sdkVersion = _services_imClient__WEBPACK_IMPORTED_MODULE_2__/* .imClient */ .wD.getSdkVersion(); + this.bindImEvents(); + await _services_imClient__WEBPACK_IMPORTED_MODULE_2__/* .imClient */ .wD.connect(this.userId, this.token); + }, + bindImEvents() { + if (this.eventsBound) { + return; + } + this.eventsBound = true; + _services_imClient__WEBPACK_IMPORTED_MODULE_2__/* .imClient */ .wD.on('connectionStatusChanged', status => { + this.connected = status.connected; + this.connectionStatusText = status.statusText || (status.connected ? '已连接' : '未连接'); + }); + }, + async logout() { + try { + await _services_imClient__WEBPACK_IMPORTED_MODULE_2__/* .imClient */ .wD.logout?.(); + } catch (error) { + this.error = error.message || '退出登录失败'; + } finally { + _services_imClient__WEBPACK_IMPORTED_MODULE_2__/* .imClient */ .wD.disconnect(); + localStorage.removeItem(STORAGE_KEY); + this.$reset(); + } + } + } +}); +/* harmony export */ __webpack_require__.d(__webpack_exports__, [ +/* harmony export */ "B", 0, /* binding */ useSessionStore +/* harmony export */ ]); + + +/***/ }), + +/***/ 7067: +/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +/* harmony import */ var _App_vue_vue_type_script_setup_true_lang_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4208); + + + +const __exports__ = _App_vue_vue_type_script_setup_true_lang_js__WEBPACK_IMPORTED_MODULE_0__/* ["default"] */ .A; + +/* harmony default export */ __webpack_exports__.A = (__exports__); + +/***/ }), + +/***/ 8409: +/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +/* harmony import */ var _DocumentedShell_vue_vue_type_script_setup_true_lang_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1106); + + + +const __exports__ = _DocumentedShell_vue_vue_type_script_setup_true_lang_js__WEBPACK_IMPORTED_MODULE_0__/* ["default"] */ .A; + +/* harmony default export */ __webpack_exports__.A = (__exports__); + +/***/ }), + +/***/ 3727: +/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +/* harmony import */ var _LoginView_vue_vue_type_script_setup_true_lang_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4524); + + + +const __exports__ = _LoginView_vue_vue_type_script_setup_true_lang_js__WEBPACK_IMPORTED_MODULE_0__/* ["default"] */ .A; + +/* harmony default export */ __webpack_exports__.A = (__exports__); + +/***/ }), + +/***/ 4208: +/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ A: function() { return /* reexport safe */ _node_modules_thread_loader_dist_cjs_js_node_modules_babel_loader_lib_index_js_clonedRuleSet_40_use_1_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_App_vue_vue_type_script_setup_true_lang_js__WEBPACK_IMPORTED_MODULE_0__.A; } +/* harmony export */ }); +/* harmony import */ var _node_modules_thread_loader_dist_cjs_js_node_modules_babel_loader_lib_index_js_clonedRuleSet_40_use_1_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_App_vue_vue_type_script_setup_true_lang_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4677); + + +/***/ }), + +/***/ 1106: +/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ A: function() { return /* reexport safe */ _node_modules_thread_loader_dist_cjs_js_node_modules_babel_loader_lib_index_js_clonedRuleSet_40_use_1_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_DocumentedShell_vue_vue_type_script_setup_true_lang_js__WEBPACK_IMPORTED_MODULE_0__.A; } +/* harmony export */ }); +/* harmony import */ var _node_modules_thread_loader_dist_cjs_js_node_modules_babel_loader_lib_index_js_clonedRuleSet_40_use_1_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_DocumentedShell_vue_vue_type_script_setup_true_lang_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(865); + + +/***/ }), + +/***/ 4524: +/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ A: function() { return /* reexport safe */ _node_modules_thread_loader_dist_cjs_js_node_modules_babel_loader_lib_index_js_clonedRuleSet_40_use_1_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_LoginView_vue_vue_type_script_setup_true_lang_js__WEBPACK_IMPORTED_MODULE_0__.A; } +/* harmony export */ }); +/* harmony import */ var _node_modules_thread_loader_dist_cjs_js_node_modules_babel_loader_lib_index_js_clonedRuleSet_40_use_1_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_LoginView_vue_vue_type_script_setup_true_lang_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(8607); + + +/***/ }) + +/******/ }); +/************************************************************************/ +/******/ // The module cache +/******/ var __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ var cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = __webpack_modules__; +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/chunk loaded */ +/******/ !function() { +/******/ var deferred = []; +/******/ __webpack_require__.O = function(result, chunkIds, fn, priority) { +/******/ if(chunkIds) { +/******/ priority = priority || 0; +/******/ for(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1]; +/******/ deferred[i] = [chunkIds, fn, priority]; +/******/ return; +/******/ } +/******/ var notFulfilled = Infinity; +/******/ for (var i = 0; i < deferred.length; i++) { +/******/ var chunkIds = deferred[i][0]; +/******/ var fn = deferred[i][1]; +/******/ var priority = deferred[i][2]; +/******/ var fulfilled = true; +/******/ for (var j = 0; j < chunkIds.length; j++) { +/******/ if ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every(function(key) { return __webpack_require__.O[key](chunkIds[j]); })) { +/******/ chunkIds.splice(j--, 1); +/******/ } else { +/******/ fulfilled = false; +/******/ if(priority < notFulfilled) notFulfilled = priority; +/******/ } +/******/ } +/******/ if(fulfilled) { +/******/ deferred.splice(i--, 1) +/******/ var r = fn(); +/******/ if (r !== undefined) result = r; +/******/ } +/******/ } +/******/ return result; +/******/ }; +/******/ }(); +/******/ +/******/ /* webpack/runtime/compat get default export */ +/******/ !function() { +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function() { return module['default']; } : +/******/ function() { return module; }; +/******/ __webpack_require__.d(getter, { a: getter }); +/******/ return getter; +/******/ }; +/******/ }(); +/******/ +/******/ /* webpack/runtime/define property getters */ +/******/ !function() { +/******/ // define getter/value functions for harmony exports +/******/ __webpack_require__.d = function(exports, definition) { +/******/ if(Array.isArray(definition)) { +/******/ var i = 0; +/******/ while(i < definition.length) { +/******/ var key = definition[i++]; +/******/ var binding = definition[i++]; +/******/ if(!__webpack_require__.o(exports, key)) { +/******/ if(binding === 0) { +/******/ Object.defineProperty(exports, key, { enumerable: true, value: definition[i++] }); +/******/ } else { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: binding }); +/******/ } +/******/ } else if(binding === 0) { i++; } +/******/ } +/******/ } else { +/******/ for(var key in definition) { +/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ } +/******/ }; +/******/ }(); +/******/ +/******/ /* webpack/runtime/global */ +/******/ !function() { +/******/ __webpack_require__.g = (function() { +/******/ if (typeof globalThis === 'object') return globalThis; +/******/ try { +/******/ return this || new Function('return this')(); +/******/ } catch (e) { +/******/ if (typeof window === 'object') return window; +/******/ } +/******/ })(); +/******/ }(); +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ !function() { +/******/ __webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); } +/******/ }(); +/******/ +/******/ /* webpack/runtime/make namespace object */ +/******/ !function() { +/******/ // define __esModule on exports +/******/ __webpack_require__.r = function(exports) { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ }(); +/******/ +/******/ /* webpack/runtime/set anonymous default export name */ +/******/ !function() { +/******/ // set .name for anonymous default exports per ES spec +/******/ __webpack_require__.dn = function(x) { +/******/ (Object.getOwnPropertyDescriptor(x, "name") || {}).writable || Object.defineProperty(x, "name", { value: "default", configurable: true }); +/******/ }; +/******/ }(); +/******/ +/******/ /* webpack/runtime/jsonp chunk loading */ +/******/ !function() { +/******/ // no baseURI +/******/ +/******/ // object to store loaded and loading chunks +/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched +/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded +/******/ var installedChunks = { +/******/ 524: 0 +/******/ }; +/******/ +/******/ // no chunk on demand loading +/******/ +/******/ // no prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ +/******/ __webpack_require__.O.j = function(chunkId) { return installedChunks[chunkId] === 0; }; +/******/ +/******/ // install a JSONP callback for chunk loading +/******/ var webpackJsonpCallback = function(parentChunkLoadingFunction, data) { +/******/ var chunkIds = data[0]; +/******/ var moreModules = data[1]; +/******/ var runtime = data[2]; +/******/ // add "moreModules" to the modules object, +/******/ // then flag all "chunkIds" as loaded and fire callback +/******/ var moduleId, chunkId, i = 0; +/******/ if(chunkIds.some(function(id) { return installedChunks[id] !== 0; })) { +/******/ for(moduleId in moreModules) { +/******/ if(__webpack_require__.o(moreModules, moduleId)) { +/******/ __webpack_require__.m[moduleId] = moreModules[moduleId]; +/******/ } +/******/ } +/******/ if(runtime) var result = runtime(__webpack_require__); +/******/ } +/******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data); +/******/ for(;i < chunkIds.length; i++) { +/******/ chunkId = chunkIds[i]; +/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { +/******/ installedChunks[chunkId][0](); +/******/ } +/******/ installedChunks[chunkId] = 0; +/******/ } +/******/ return __webpack_require__.O(result); +/******/ } +/******/ +/******/ var chunkLoadingGlobal = self["webpackChunkjc_web_im"] = self["webpackChunkjc_web_im"] || []; +/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); +/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal)); +/******/ }(); +/******/ +/************************************************************************/ +/******/ +/******/ // startup +/******/ // Load entry module and return exports +/******/ // This entry module depends on other loaded chunks and execution need to be delayed +/******/ var __webpack_exports__ = __webpack_require__.O(undefined, [504], function() { return __webpack_require__(1406); }) +/******/ __webpack_exports__ = __webpack_require__.O(__webpack_exports__); +/******/ +/******/ })() +; +//# sourceMappingURL=app.26bc1008.js.map \ No newline at end of file diff --git a/dist/js/app.26bc1008.js.map b/dist/js/app.26bc1008.js.map new file mode 100644 index 0000000..09c03b5 --- /dev/null +++ b/dist/js/app.26bc1008.js.map @@ -0,0 +1 @@ +{"version":3,"file":"js/app.26bc1008.js","mappings":"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K,kEAAQ,CAACka,UAAU,CAAC,CAAC;QACrB9N,YAAY,CAACmX,UAAU,CAACV,WAAW,CAAC;QACpC,IAAI,CAACiB,MAAM,CAAC,CAAC;MACf;IACF;EACF;AACF,CAAC,CAAC,C;;;;;;;;;;;;AChO+D;AACL;;AAE5D,oBAAoB,4FAAM;;AAE1B,sDAAe,W;;;;;;;;ACL8D;AACL;;AAExE,oBAAoB,wGAAM;;AAE1B,sDAAe,W;;;;;;;;ACLwD;AACL;;AAElE,oBAAoB,kGAAM;;AAE1B,sDAAe,W;;;;;;;;;;;ACLmO,C;;;;;;;;;;;ACAqB,C;;;;;;;;;;;ACAN,C;;;;;;UCAjQ;UACA;;UAEA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;;UAEA;UACA;;UAEA;UACA;UACA;;UAEA;UACA;;;;;WCzBA;WACA;WACA;WACA;WACA,+BAA+B,wCAAwC;WACvE;WACA;WACA;WACA;WACA,iBAAiB,qBAAqB;WACtC;WACA;WACA;WACA;WACA,kBAAkB,qBAAqB;WACvC,oHAAoH,iDAAiD;WACrK;WACA,KAAK;WACL;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA,E;;;;;WC7BA;WACA;WACA;WACA,eAAe,4BAA4B;WAC3C,eAAe;WACf,iCAAiC,WAAW;WAC5C;WACA,E;;;;;WCPA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA,2CAA2C,0CAA0C;WACrF,MAAM;WACN,2CAA2C,gCAAgC;WAC3E;WACA,KAAK,yBAAyB;WAC9B;WACA,GAAG;WACH;WACA;WACA,0CAA0C,wCAAwC;WAClF;WACA;WACA;WACA,E;;;;;WCtBA;WACA;WACA;WACA;WACA,GAAG;WACH;WACA;WACA,CAAC,I;;;;;WCPD,8CAA8C,yD;;;;;WCA9C;WACA;WACA;WACA,uDAAuD,iBAAiB;WACxE;WACA,gDAAgD,aAAa;WAC7D,E;;;;;WCNA;WACA;WACA,kDAAkD,iDAAiD,sCAAsC;WACzI,E;;;;;WCHA;;WAEA;WACA;WACA;WACA;WACA;WACA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA,8CAA8C;;WAE9C;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA,iCAAiC,mCAAmC;WACpE;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA,MAAM,qBAAqB;WAC3B;WACA;WACA;WACA;WACA;WACA;WACA;WACA;;WAEA;WACA;WACA,4G;;;;;UElDA;UACA;UACA;UACA,+EAA+E,mCAAmC;UAClH","sources":["webpack://jc-web-im/./src/App.vue","webpack://jc-web-im/./src/components/DocumentedShell.vue","webpack://jc-web-im/./src/components/LoginView.vue","webpack://jc-web-im/./src/config.js","webpack://jc-web-im/./src/main.js","webpack://jc-web-im/./src/services/appServerClient.js","webpack://jc-web-im/./src/services/documentedHttpImClient.js","webpack://jc-web-im/./src/services/httpImClient.js","webpack://jc-web-im/./src/services/imClient.js","webpack://jc-web-im/./src/services/longConnectionImClient.js","webpack://jc-web-im/./src/stores/session.js","webpack://jc-web-im/./src/App.vue?7ccd","webpack://jc-web-im/./src/components/DocumentedShell.vue?4923","webpack://jc-web-im/./src/components/LoginView.vue?3311","webpack://jc-web-im/./src/App.vue?455b","webpack://jc-web-im/./src/components/DocumentedShell.vue?08ea","webpack://jc-web-im/./src/components/LoginView.vue?f595","webpack://jc-web-im/webpack/bootstrap","webpack://jc-web-im/webpack/runtime/chunk loaded","webpack://jc-web-im/webpack/runtime/compat get default export","webpack://jc-web-im/webpack/runtime/define property getters","webpack://jc-web-im/webpack/runtime/global","webpack://jc-web-im/webpack/runtime/hasOwnProperty shorthand","webpack://jc-web-im/webpack/runtime/make namespace object","webpack://jc-web-im/webpack/runtime/set anonymous default export name","webpack://jc-web-im/webpack/runtime/jsonp chunk loading","webpack://jc-web-im/webpack/before-startup","webpack://jc-web-im/webpack/startup","webpack://jc-web-im/webpack/after-startup"],"sourcesContent":["\n\n\n","\n\n\n","\n\n\n","const currentHost = window.location.hostname || '127.0.0.1';\nconst runtimeConfig = window.JC_WEB_IM_CONFIG || {};\nconst appServerOrigin = `${window.location.protocol}//${currentHost}:8888`;\nconst qrAppServerOrigin = `${window.location.protocol}//${currentHost}:18888`;\nconst adminServerOrigin = `${window.location.protocol}//${currentHost}:8024`;\n\r\nexport const appConfig = {\r\n appName: 'Web IM',\r\n adminServer: runtimeConfig.adminServer || process.env.VUE_APP_ADMIN_SERVER || adminServerOrigin,\n appServer: runtimeConfig.appServer || process.env.VUE_APP_APP_SERVER || appServerOrigin,\n qrAppServer: runtimeConfig.qrAppServer || process.env.VUE_APP_QR_APP_SERVER || qrAppServerOrigin,\n platformConfigApi: runtimeConfig.platformConfigApi || process.env.VUE_APP_PLATFORM_CONFIG_API || (process.env.NODE_ENV === 'development' ? '/local-api/platform_config.php' : '/api/platform_config.php'),\n registerConfigApi: runtimeConfig.registerConfigApi || process.env.VUE_APP_REGISTER_CONFIG_API || `${adminServerOrigin}/api/register-config`,\r\n registerPhoneExistsApi: runtimeConfig.registerPhoneExistsApi || process.env.VUE_APP_REGISTER_PHONE_EXISTS_API || `${adminServerOrigin}/api/register-phone-exists`,\r\n contactsApi: runtimeConfig.contactsApi || process.env.VUE_APP_CONTACTS_API || (process.env.NODE_ENV === 'development' ? '/local-api/contacts.php' : '/api/contacts.php'),\r\n updateAliasApi: runtimeConfig.updateAliasApi || process.env.VUE_APP_UPDATE_ALIAS_API || (process.env.NODE_ENV === 'development' ? '/local-api/update_alias.php' : '/api/update_alias.php'),\r\n addContactApi: runtimeConfig.addContactApi || process.env.VUE_APP_ADD_CONTACT_API || (process.env.NODE_ENV === 'development' ? '/local-api/add_contact.php' : '/api/add_contact.php'),\r\n conversationActionsApi: runtimeConfig.conversationActionsApi || process.env.VUE_APP_CONVERSATION_ACTIONS_API || (process.env.NODE_ENV === 'development' ? '/local-api/conversation_actions.php' : '/api/conversation_actions.php'),\r\n sendMessageApi: runtimeConfig.sendMessageApi || process.env.VUE_APP_SEND_MESSAGE_API || (process.env.NODE_ENV === 'development' ? '/local-api/send_message.php' : '/api/send_message.php'),\r\n messageActionApi: runtimeConfig.messageActionApi || process.env.VUE_APP_MESSAGE_ACTION_API || (process.env.NODE_ENV === 'development' ? '/local-api/message_action.php' : '/api/message_action.php'),\r\n messageDetailApi: runtimeConfig.messageDetailApi || process.env.VUE_APP_MESSAGE_DETAIL_API || (process.env.NODE_ENV === 'development' ? '/local-api/message_detail.php' : '/api/message_detail.php'),\r\n messagesApi: runtimeConfig.messagesApi || process.env.VUE_APP_MESSAGES_API || (process.env.NODE_ENV === 'development' ? '/local-api/messages.php' : '/api/messages.php'),\r\n profileApi: runtimeConfig.profileApi || process.env.VUE_APP_PROFILE_API || (process.env.NODE_ENV === 'development' ? '/local-api/profile.php' : '/api/profile.php'),\r\n changePasswordApi: runtimeConfig.changePasswordApi || process.env.VUE_APP_CHANGE_PASSWORD_API || `${appServerOrigin}/auth/changePassword`,\n logoutApi: runtimeConfig.logoutApi || process.env.VUE_APP_LOGOUT_API || `${appServerOrigin}/auth/logout`,\n imServerHost: runtimeConfig.imServerHost || process.env.VUE_APP_IM_SERVER_HOST || currentHost,\n imRoutePort: Number(runtimeConfig.imRoutePort || process.env.VUE_APP_IM_ROUTE_PORT || 18000),\r\n useWss: runtimeConfig.useWss ?? process.env.VUE_APP_USE_WSS === 'true',\n wsPath: runtimeConfig.wsPath || process.env.VUE_APP_WS_PATH || '/ws',\n wsUrl: runtimeConfig.wsUrl || process.env.VUE_APP_WS_URL || '',\n sdkMode: runtimeConfig.sdkMode || process.env.VUE_APP_IM_MODE || 'http',\n syncIntervalMs: Number(runtimeConfig.syncIntervalMs || process.env.VUE_APP_SYNC_INTERVAL_MS || 300),\n platform: 5,\n clientIdStorageKey: 'qmt-web-im-client-id',\n authTokenStoragePrefix: 'qmt-web-im-auth-token-'\n};\n","import { createApp } from 'vue';\r\nimport { createPinia } from 'pinia';\r\nimport App from './App.vue';\r\nimport './styles/base.css';\r\n\r\ncreateApp(App).use(createPinia()).mount('#app');\r\n","import axios from 'axios';\nimport { appConfig } from '@/config';\n\nconst AES_KEY = 'xczx2026!@#secKe';\nconst ENCRYPTION_EXCLUDE_PATHS = ['/avatar/', '/f/', '/amr2mp3', '/appVersion/check'];\nconst AUTH_STORAGE_KEY = 'qmt-web-im-app-auth-v1';\nconst AUTH_ERROR_CODE = 13;\nconst REFRESH_ERROR_CODE = 20045;\n\nlet importedCryptoKeyPromise = null;\nlet refreshPromise = null;\n\nfunction storageScope() {\n return appConfig.appServer || '';\n}\n\nfunction readAuthStore() {\n try {\n return JSON.parse(localStorage.getItem(AUTH_STORAGE_KEY) || '{}');\n } catch (error) {\n return {};\n }\n}\n\nfunction writeAuthStore(store) {\n localStorage.setItem(AUTH_STORAGE_KEY, JSON.stringify(store));\n}\n\nfunction normalizeAuthBundle(bundle = {}) {\n const accessToken = String(bundle.accessToken || '').trim();\n const refreshToken = String(bundle.refreshToken || '').trim();\n if (!accessToken || !refreshToken) {\n return null;\n }\n return {\n accessToken,\n refreshToken,\n tokenType: String(bundle.tokenType || 'Bearer').trim() || 'Bearer',\n accessTokenExpireIn: Number(bundle.accessTokenExpireIn || 0) || 0,\n accessTokenExpireAt: Number(bundle.accessTokenExpireAt || 0) || 0,\n refreshTokenExpireIn: Number(bundle.refreshTokenExpireIn || 0) || 0,\n refreshTokenExpireAt: Number(bundle.refreshTokenExpireAt || 0) || 0\n };\n}\n\nexport function getStoredAppAuth() {\n const store = readAuthStore();\n return normalizeAuthBundle(store[storageScope()]);\n}\n\nexport function persistAppAuth(bundle) {\n const normalized = normalizeAuthBundle(bundle);\n const store = readAuthStore();\n if (!normalized) {\n delete store[storageScope()];\n writeAuthStore(store);\n return null;\n }\n store[storageScope()] = normalized;\n writeAuthStore(store);\n return normalized;\n}\n\nexport function clearStoredAppAuth() {\n persistAppAuth(null);\n}\n\nfunction hasFormData(value) {\n return typeof FormData !== 'undefined' && value instanceof FormData;\n}\n\nfunction absoluteUrl(url) {\n return new URL(url, window.location.origin);\n}\n\nfunction shouldEncrypt(url) {\n const pathname = absoluteUrl(url).pathname;\n return !ENCRYPTION_EXCLUDE_PATHS.some(path => pathname === path || pathname.startsWith(path));\n}\n\nfunction shouldAttachAuth(config) {\n return !config.skipAuth;\n}\n\nfunction textEncoder() {\n return new TextEncoder();\n}\n\nfunction textDecoder() {\n return new TextDecoder();\n}\n\nfunction bytesToBase64(bytes) {\n let binary = '';\n bytes.forEach(byte => {\n binary += String.fromCharCode(byte);\n });\n return btoa(binary);\n}\n\nfunction base64ToBytes(base64) {\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let index = 0; index < binary.length; index += 1) {\n bytes[index] = binary.charCodeAt(index);\n }\n return bytes;\n}\n\nasync function getCryptoKey() {\n if (!importedCryptoKeyPromise) {\n importedCryptoKeyPromise = window.crypto.subtle.importKey(\n 'raw',\n textEncoder().encode(AES_KEY),\n { name: 'AES-CBC' },\n false,\n ['encrypt', 'decrypt']\n );\n }\n return importedCryptoKeyPromise;\n}\n\nasync function encryptPayload(plainText) {\n const iv = window.crypto.getRandomValues(new Uint8Array(16));\n const key = await getCryptoKey();\n const encrypted = await window.crypto.subtle.encrypt(\n { name: 'AES-CBC', iv },\n key,\n textEncoder().encode(plainText)\n );\n return `${bytesToBase64(iv)}:${bytesToBase64(new Uint8Array(encrypted))}`;\n}\n\nasync function decryptPayload(cipherText) {\n const [ivBase64, encryptedBase64] = String(cipherText || '').split(':', 2);\n if (!ivBase64 || !encryptedBase64) {\n throw new Error('Encrypted payload format is invalid');\n }\n const key = await getCryptoKey();\n const decrypted = await window.crypto.subtle.decrypt(\n { name: 'AES-CBC', iv: base64ToBytes(ivBase64) },\n key,\n base64ToBytes(encryptedBase64)\n );\n return textDecoder().decode(decrypted);\n}\n\nfunction normalizeBusinessResponse(payload) {\n if (payload && typeof payload === 'object' && !Array.isArray(payload)) {\n return payload;\n }\n return {\n code: 0,\n message: 'success',\n data: payload\n };\n}\n\nasync function decodeResponsePayload(response) {\n const encrypted = String(response.headers?.['x-encrypted'] || '').toLowerCase() === 'true';\n if (encrypted && typeof response.data === 'string') {\n const plainText = await decryptPayload(response.data);\n response.data = normalizeBusinessResponse(JSON.parse(plainText));\n return response;\n }\n response.data = normalizeBusinessResponse(response.data);\n return response;\n}\n\nconst client = axios.create({\n withCredentials: false\n});\n\nclient.interceptors.request.use(async config => {\n const nextConfig = { ...config };\n const url = absoluteUrl(nextConfig.url || '').toString();\n\n if (shouldAttachAuth(nextConfig)) {\n const auth = getStoredAppAuth();\n if (auth?.accessToken) {\n nextConfig.headers = {\n ...(nextConfig.headers || {}),\n Authorization: `${auth.tokenType || 'Bearer'} ${auth.accessToken}`\n };\n }\n }\n\n if (!hasFormData(nextConfig.data) && nextConfig.method?.toLowerCase() !== 'get' && shouldEncrypt(url)) {\n const plainBody = JSON.stringify(nextConfig.data ?? {});\n nextConfig.data = await encryptPayload(plainBody);\n nextConfig.headers = {\n ...(nextConfig.headers || {}),\n 'Content-Type': 'text/plain;charset=UTF-8',\n 'X-Timestamp': `${Math.floor(Date.now() / 1000)}`\n };\n }\n\n return nextConfig;\n});\n\nclient.interceptors.response.use(async response => decodeResponsePayload(response));\n\nasync function refreshAccessToken() {\n if (refreshPromise) {\n return refreshPromise;\n }\n const auth = getStoredAppAuth();\n if (!auth?.refreshToken) {\n clearStoredAppAuth();\n throw new Error('登录状态已失效,请重新登录');\n }\n\n refreshPromise = client.post(`${appConfig.appServer}/auth/refreshToken`, {\n refreshToken: auth.refreshToken\n }, {\n skipAuth: true\n }).then(response => {\n const payload = response.data || {};\n if (Number(payload.code) !== 0 || !payload.data) {\n if (Number(payload.code) === REFRESH_ERROR_CODE) {\n clearStoredAppAuth();\n }\n throw new Error(payload.message || '刷新登录状态失败');\n }\n persistAppAuth(payload.data);\n return payload.data;\n }).finally(() => {\n refreshPromise = null;\n });\n\n return refreshPromise;\n}\n\nasync function request(config) {\n const response = await client.request(config);\n const payload = response.data || {};\n if (Number(payload.code) === AUTH_ERROR_CODE && shouldAttachAuth(config) && !config._retriedAfterRefresh) {\n await refreshAccessToken();\n return request({\n ...config,\n _retriedAfterRefresh: true\n });\n }\n return response;\n}\n\nexport async function appServerRequest(config) {\n return request(config);\n}\n\nexport async function appServerGet(url, config = {}) {\n return request({\n ...config,\n method: 'get',\n url\n });\n}\n\nexport async function appServerPost(url, data = {}, config = {}) {\n return request({\n ...config,\n method: 'post',\n url,\n data\n });\n}\n","import { appConfig } from '@/config';\nimport { HttpImClient } from './httpImClient';\nimport axios from 'axios';\nimport {\n appServerGet,\n appServerPost,\n clearStoredAppAuth,\n getStoredAppAuth,\n persistAppAuth\n} from './appServerClient';\n\nfunction inferAccountType(account) {\n return String(account || '').includes('@') ? 2 : 1;\n}\n\nfunction unwrapData(response) {\n return response.data?.data ?? response.data?.result ?? null;\n}\n\nfunction unwrapMessage(response, fallback) {\n return response.data?.message || response.data?.msg || fallback;\n}\n\nexport class DocumentedHttpImClient extends HttpImClient {\n unsupported(feature) {\n throw new Error(`当前对接文档未提供“${feature}”接口,已禁用旧本地代理链路`);\n }\n\n async loginWithPassword(account, password, type = inferAccountType(account)) {\n const response = await appServerPost(`${appConfig.appServer}/auth/login_pwd`, {\n type,\n number: account,\n password,\n platform: appConfig.platform,\n clientId: this.getClientId()\n }, {\n skipAuth: true\n });\n\n if (response.data?.code !== 0) {\n throw new Error(unwrapMessage(response, '登录失败'));\n }\n\n persistAppAuth(unwrapData(response));\n return unwrapData(response);\n }\n\n async requestAuthCode(email) {\n const response = await appServerPost(`${appConfig.appServer}/auth/sendEmailCode`, {\n email\n }, {\n skipAuth: true\n });\n\n if (response.data?.code !== 0) {\n throw new Error(unwrapMessage(response, '验证码发送失败'));\n }\n\n return unwrapData(response);\n }\n\n async loginWithCode(account, code) {\n void account;\n void code;\n throw new Error('接口文档未提供验证码登录接口,请使用密码登录');\n }\n\n async register({ type, number, code = '', password, invitationCode = '' }) {\n const response = await appServerPost(`${appConfig.appServer}/auth/register`, {\n type,\n number,\n code,\n password,\n clientId: this.getClientId(),\n platform: appConfig.platform,\n invitationCode\n }, {\n skipAuth: true\n });\n\n if (response.data?.code !== 0) {\n throw new Error(unwrapMessage(response, '注册失败'));\n }\n\n persistAppAuth(unwrapData(response));\n return unwrapData(response);\n }\n\n async createPcSession(userId = '') {\n const response = await axios.post(`${appConfig.qrAppServer}/pc_session`, {\n flag: 1,\n device_name: 'Web IM',\n userId,\n clientId: this.getClientId(),\n platform: appConfig.platform\n }, { withCredentials: false });\n\n if (response.data?.code !== 0) {\n throw new Error(unwrapMessage(response, '二维码登录会话创建失败'));\n }\n\n return unwrapData(response) || {};\n }\n\n async loginWithPcSession(token) {\n const url = `${appConfig.qrAppServer}/session_login/${encodeURIComponent(token)}`;\n let response;\n try {\n response = await axios.post(url, null, { withCredentials: false });\n } catch (postError) {\n response = await axios.get(url, { withCredentials: false });\n }\n\n this.persistAppToken(url, response);\n\n const payload = response.data || {};\n const result = payload.result ?? payload.data ?? null;\n\n if (Number(payload.code) === 0 && result) {\n persistAppAuth(result);\n }\n\n if ([0, 8, 9, 10, 13, 18, 20042].includes(Number(payload.code))) {\n return {\n ...payload,\n data: result\n };\n }\n\n throw new Error(payload.message || '扫码登录失败');\n }\n\n async changePassword({ oldPassword, newPassword }) {\n const response = await appServerPost(appConfig.changePasswordApi, {\n oldPassword,\n newPassword\n });\n\n if (response.data?.code !== 0) {\n throw new Error(unwrapMessage(response, '密码修改失败'));\n }\n\n return true;\n }\n\n async resetPasswordWithCode({ mobile, resetCode, newPassword }) {\n void mobile;\n void resetCode;\n void newPassword;\n throw new Error('接口文档未提供重置密码接口,请联系服务端确认流程');\n }\n\n async logout() {\n const auth = getStoredAppAuth();\n if (!auth?.refreshToken) {\n clearStoredAppAuth();\n return true;\n }\n\n try {\n const response = await appServerPost(appConfig.logoutApi, {\n refreshToken: auth.refreshToken\n });\n if (response.data?.code !== 0) {\n throw new Error(unwrapMessage(response, '退出登录失败'));\n }\n } finally {\n clearStoredAppAuth();\n }\n\n return true;\n }\n\n async fetchRemoteConversations() {\n return [];\n }\n\n async fetchRemoteMessages() {\n return [];\n }\n\n async getServerContactList() {\n return [];\n }\n\n async getProfile() {\n return null;\n }\n\n async sendTextMessage() {\n this.unsupported('发送消息');\n }\n\n async sendAttachmentMessage() {\n this.unsupported('发送附件消息');\n }\n\n async addContact() {\n this.unsupported('添加联系人');\n }\n\n async updateContactAlias() {\n this.unsupported('联系人备注');\n }\n\n async createGroup() {\n this.unsupported('创建群组');\n }\n\n async joinGroup() {\n this.unsupported('加入群组');\n }\n\n async broadcastMessage() {\n this.unsupported('群发消息');\n }\n\n async getGroupDetail() {\n this.unsupported('群详情');\n }\n\n async getGroupMembers() {\n this.unsupported('群成员');\n }\n\n async updateGroupAlias() {\n this.unsupported('群备注');\n }\n\n async leaveGroup() {\n this.unsupported('退出群组');\n }\n\n async dismissGroup() {\n this.unsupported('解散群组');\n }\n\n async transferGroup() {\n this.unsupported('转让群组');\n }\n\n async setGroupAdmins() {\n this.unsupported('设置群管理员');\n }\n\n async updateGroupProfile() {\n this.unsupported('更新群资料');\n }\n\n async kickGroupMembers() {\n this.unsupported('移除群成员');\n }\n\n async updateGroupMuteSetting() {\n this.unsupported('群禁言设置');\n }\n\n async updateProfile() {\n this.unsupported('更新个人资料');\n }\n\n async recallMessage() {\n this.unsupported('撤回消息');\n }\n\n async editMessage() {\n this.unsupported('编辑消息');\n }\n\n async getMessageDetail() {\n this.unsupported('消息详情');\n }\n}\n","import axios from 'axios';\nimport { appConfig } from '@/config';\nimport {\n appServerGet,\n appServerPost,\n clearStoredAppAuth,\n getStoredAppAuth,\n persistAppAuth\n} from '@/services/appServerClient';\n\r\nconst LOCAL_MESSAGE_STORAGE_KEY = 'qmt-web-im-local-messages-v2';\r\nconst LOCAL_HIDDEN_MESSAGE_STORAGE_KEY = 'qmt-web-im-hidden-messages-v1';\r\nconst LOCAL_RECALLED_MESSAGE_STORAGE_KEY = 'qmt-web-im-recalled-messages-v1';\r\n\r\nfunction scopedStorageKey(baseKey, userId = '') {\r\n const scope = `${appConfig.appServer || ''}:${userId || 'anonymous'}`;\r\n return `${baseKey}:${encodeURIComponent(scope)}`;\r\n}\r\n\r\nfunction randomId() {\r\n if (window.crypto && window.crypto.randomUUID) {\r\n return window.crypto.randomUUID().replaceAll('-', '');\r\n }\r\n return `${Date.now()}${Math.random().toString(16).slice(2)}`;\r\n}\r\n\r\nfunction readLocalMessages(userId) {\r\n try {\r\n return JSON.parse(localStorage.getItem(scopedStorageKey(LOCAL_MESSAGE_STORAGE_KEY, userId)) || '{}');\r\n } catch (error) {\r\n return {};\r\n }\r\n}\r\n\r\nfunction readMessageSet(storageKey, userId) {\r\n try {\r\n return JSON.parse(localStorage.getItem(scopedStorageKey(storageKey, userId)) || '{}');\r\n } catch (error) {\r\n return {};\r\n }\r\n}\r\n\r\nfunction writeMessageSet(storageKey, messagesByConversation, userId) {\r\n localStorage.setItem(scopedStorageKey(storageKey, userId), JSON.stringify(messagesByConversation));\r\n}\r\n\r\nfunction messageKeys(message) {\r\n return [message?.id, message?.messageUid]\r\n .filter(Boolean)\r\n .map(value => String(value));\r\n}\r\n\r\nfunction hasStoredMessage(storageKey, conversationId, message, userId) {\r\n const stored = readMessageSet(storageKey, userId);\r\n const ids = new Set(stored[conversationId] || []);\r\n return messageKeys(message).some(id => ids.has(id));\r\n}\r\n\r\nfunction storeMessageKeys(storageKey, conversationId, messages, userId) {\r\n const stored = readMessageSet(storageKey, userId);\r\n const ids = new Set(stored[conversationId] || []);\r\n messages.forEach(message => {\r\n messageKeys(message).forEach(id => ids.add(id));\r\n });\r\n stored[conversationId] = Array.from(ids);\r\n writeMessageSet(storageKey, stored, userId);\r\n}\r\n\r\nfunction withRecalledState(message) {\r\n return {\r\n ...message,\r\n status: 'recalled',\r\n deliveryStatus: '',\r\n recalled: true,\r\n recallStatus: '已撤回'\r\n };\r\n}\r\n\r\nfunction applyLocalMessageState(conversationId, messages, userId) {\r\n return messages\r\n .filter(message => !hasStoredMessage(LOCAL_HIDDEN_MESSAGE_STORAGE_KEY, conversationId, message, userId))\r\n .map(message => {\r\n const isRecalled = Boolean(message.recalled)\r\n || message.status === 'recalled'\r\n || hasStoredMessage(LOCAL_RECALLED_MESSAGE_STORAGE_KEY, conversationId, message, userId);\r\n if (!isRecalled) {\r\n return message;\r\n }\r\n return withRecalledState(message);\r\n });\r\n}\r\n\r\nfunction writeLocalMessages(messagesByConversation, userId) {\r\n localStorage.setItem(scopedStorageKey(LOCAL_MESSAGE_STORAGE_KEY, userId), JSON.stringify(messagesByConversation));\r\n}\r\n\r\nfunction saveLocalMessage(message, userId) {\r\n const messagesByConversation = readLocalMessages(userId);\r\n messagesByConversation[message.conversationId] = messagesByConversation[message.conversationId] || [];\r\n const index = messagesByConversation[message.conversationId].findIndex(item => item.id === message.id);\r\n if (index >= 0) {\r\n messagesByConversation[message.conversationId].splice(index, 1, message);\r\n } else {\r\n messagesByConversation[message.conversationId].push(message);\r\n }\r\n writeLocalMessages(messagesByConversation, userId);\r\n}\r\n\r\nfunction mergeSyncedMessages(localMessages, remoteMessages, conversationId, userId) {\r\n const visibleLocalMessages = applyLocalMessageState(conversationId, localMessages, userId);\r\n const localByKey = new Map();\r\n visibleLocalMessages.forEach(message => {\r\n messageKeys(message).forEach(key => {\r\n if (!localByKey.has(key)) {\r\n localByKey.set(key, message);\r\n }\r\n });\r\n });\r\n\r\n const visibleRemoteMessages = applyLocalMessageState(conversationId, remoteMessages, userId).map(remoteMessage => {\r\n if (!remoteMessage.recalled || String(remoteMessage.text || '').trim()) {\r\n return remoteMessage;\r\n }\r\n const localMessage = messageKeys(remoteMessage)\r\n .map(key => localByKey.get(key))\r\n .find(message => message?.text && message.text !== '你撤回了一条消息');\r\n return localMessage ? { ...remoteMessage, text: localMessage.text } : remoteMessage;\r\n });\r\n const remoteKeys = new Set();\r\n visibleRemoteMessages.forEach(message => {\r\n messageKeys(message).forEach(key => remoteKeys.add(key));\r\n });\r\n const now = Date.now();\r\n\r\n function hasRemoteMatch(localMessage) {\r\n if (localMessage.messageUid && remoteKeys.has(localMessage.messageUid)) {\r\n return true;\r\n }\r\n return visibleRemoteMessages.some(remoteMessage => {\r\n return remoteMessage.direction === localMessage.direction\r\n && remoteMessage.text === localMessage.text\r\n && Math.abs(Number(remoteMessage.timestamp || 0) - Number(localMessage.timestamp || 0)) < 15000;\r\n });\r\n }\r\n\r\n const pendingLocalMessages = visibleLocalMessages.filter(message => {\r\n if (message.status === 'recalled' || message.recalled) {\r\n return message.direction === 'out' && !hasRemoteMatch(message);\r\n }\r\n if (message.direction !== 'out') {\r\n return false;\r\n }\r\n if (message.status === 'sent' && message.messageUid) {\r\n return !hasRemoteMatch(message);\r\n }\r\n if (message.status === 'sent') {\r\n return !hasRemoteMatch(message) && now - Number(message.timestamp || 0) < 30000;\r\n }\r\n return message.status === 'sending' || message.status === 'failed';\r\n });\r\n\r\n return [...visibleRemoteMessages, ...pendingLocalMessages].sort((a, b) => {\r\n return Number(a.displayTimestamp || a.timestamp || 0) - Number(b.displayTimestamp || b.timestamp || 0);\r\n });\r\n}\r\n\r\nfunction getTitle(value, fallback) {\r\n return value || fallback || '';\r\n}\r\n\r\nfunction getUniqueEquipment(mobile, fallback) {\r\n const allowedDevices = {\r\n '18301902119': '67072d37062c4f31b1165073552933ba',\r\n '18301902116': '1777479418113eeea8e236edce',\r\n '18301902118': 'de89fb2f2a61480a8c381f6c16e28bd8',\r\n '18301902117': 'd117f480faa64145b6162107b1b49617'\r\n };\r\n return allowedDevices[mobile] || localStorage.getItem('uniqueEquipment') || fallback;\r\n}\r\n\r\nfunction canUseLocalLoginFallback(response) {\r\n const message = response.data?.message || '';\r\n return process.env.NODE_ENV === 'development' && message.includes('允许此设备登录');\r\n}\r\n\r\nexport class HttpImClient {\r\n constructor() {\r\n this.listeners = new Map();\r\n this.connected = false;\r\n this.currentUser = null;\r\n }\r\n\r\n getClientId() {\r\n let clientId = localStorage.getItem(appConfig.clientIdStorageKey);\r\n if (!clientId) {\r\n clientId = randomId();\r\n localStorage.setItem(appConfig.clientIdStorageKey, clientId);\r\n }\r\n return clientId;\r\n }\r\n\r\n restoreClientId(clientId) {\r\n if (clientId) {\r\n localStorage.setItem(appConfig.clientIdStorageKey, clientId);\r\n }\r\n }\r\n\r\n getSdkVersion() {\r\n return 'HTTP';\r\n }\r\n\r\n async ensureSdk() {\r\n return true;\r\n }\r\n\r\n async loginWithPassword(mobile, password) {\r\n const url = `${appConfig.appServer}/login_pwd`;\r\n const deviceId = getUniqueEquipment(mobile, this.getClientId());\r\n const response = await axios.post(url, {\r\n mobile,\r\n password,\r\n platform: appConfig.platform,\r\n clientId: deviceId,\r\n uniqueEquipment: deviceId\r\n }, { withCredentials: false });\r\n\r\n this.persistAppToken(url, response);\r\n if (response.data?.code !== 0) {\r\n if (canUseLocalLoginFallback(response)) {\r\n return {\r\n userId: mobile,\r\n token: `local-device-pending-${Date.now()}`,\r\n displayName: mobile,\r\n localOnly: true\r\n };\r\n }\r\n throw new Error(response.data?.message || '登录失败');\r\n }\r\n return response.data.result;\r\n }\r\n\r\n async requestAuthCode(mobile) {\r\n const response = await axios.post(`${appConfig.appServer}/send_code`, { mobile }, { withCredentials: false });\r\n if (response.data?.code !== 0) {\r\n throw new Error(response.data?.message || '验证码发送失败');\r\n }\r\n return response.data.result;\r\n }\r\n\r\n async loginWithCode(mobile, code) {\r\n const url = `${appConfig.appServer}/login`;\r\n const deviceId = getUniqueEquipment(mobile, this.getClientId());\r\n const response = await axios.post(url, {\r\n mobile,\r\n code,\r\n platform: appConfig.platform,\r\n clientId: deviceId,\r\n uniqueEquipment: deviceId\r\n }, { withCredentials: false });\r\n\r\n this.persistAppToken(url, response);\r\n if (response.data?.code !== 0) {\r\n throw new Error(response.data?.message || '登录失败');\r\n }\r\n return response.data.result;\r\n }\r\n\r\n async createPcSession(userId = '') {\r\n const response = await axios.post(`${appConfig.appServer}/pc_session`, {\r\n flag: 1,\r\n device_name: 'Web IM',\r\n userId,\r\n clientId: this.getClientId(),\r\n platform: appConfig.platform\r\n }, { withCredentials: false });\r\n\r\n if (response.data?.code !== 0) {\r\n throw new Error(response.data?.message || '二维码登录会话创建失败');\r\n }\r\n return response.data.result || {};\r\n }\r\n\r\n async loginWithPcSession(token) {\r\n const url = `${appConfig.appServer}/session_login/${encodeURIComponent(token)}`;\r\n const response = await axios.post(url, null, { withCredentials: false });\r\n this.persistAppToken(url, response);\r\n const data = response.data || {};\r\n if ([0, 8, 9, 10, 18].includes(Number(data.code))) {\r\n return data;\r\n }\r\n throw new Error(data.message || '扫码登录失败');\r\n }\r\n\r\n async registerWithPhone(mobile, code) {\r\n const url = `${appConfig.appServer}/register`;\r\n const deviceId = getUniqueEquipment(mobile, this.getClientId());\r\n\r\n try {\r\n const response = await axios.post(url, {\r\n mobile,\r\n code,\r\n platform: appConfig.platform,\r\n clientId: deviceId,\r\n uniqueEquipment: deviceId\r\n }, { withCredentials: false });\r\n\r\n this.persistAppToken(url, response);\r\n if (response.data?.code !== 0) {\r\n throw new Error(response.data?.message || '注册失败');\r\n }\r\n return response.data.result;\r\n } catch (error) {\r\n const status = error.response?.status;\r\n if (status === 404 || status === 405) {\r\n return this.loginWithCode(mobile, code);\r\n }\r\n throw error;\r\n }\r\n }\r\n\r\n connect(userId, token) {\r\n this.currentUser = { userId, token };\r\n this.connected = true;\r\n this.emit('connectionStatusChanged', { connected: true, statusText: '已连接' });\r\n this.refreshConversationList();\r\n }\r\n\r\n disconnect() {\r\n this.connected = false;\r\n this.currentUser = null;\r\n this.emit('connectionStatusChanged', { connected: false, statusText: '未连接' });\r\n }\r\n\r\n getConversationList() {\r\n return [];\r\n }\r\n\r\n refreshConversationList() {\r\n const conversations = this.getConversationList();\r\n if (conversations.length) {\r\n this.emit('conversationsChanged', conversations);\r\n }\r\n if (this.currentUser?.userId) {\r\n this.fetchRemoteConversations().then(remoteConversations => {\r\n this.emit('conversationsChanged', remoteConversations);\r\n }).catch(() => {});\r\n }\r\n return conversations;\r\n }\r\n\r\n async fetchRemoteConversations(limit = 50) {\r\n if (!this.currentUser?.userId) {\r\n return [];\r\n }\r\n const response = await axios.post(appConfig.messagesApi, {\r\n userId: this.currentUser.userId,\r\n limit\r\n }, { withCredentials: false });\r\n\r\n if (response.data?.code !== 0) {\r\n throw new Error(response.data?.message || '会话同步失败');\r\n }\r\n\r\n return (response.data?.result?.conversations || []).map(conversation => ({\r\n ...conversation,\r\n updatedAt: Number(conversation.updatedAt || Date.now()),\r\n avatarText: conversation.avatarText || String(conversation.title || conversation.target || '?').slice(0, 1).toUpperCase(),\r\n raw: conversation.raw || { type: 'single', target: conversation.target, line: 0 }\r\n }));\r\n }\r\n\r\n getMessages(conversation) {\r\n if (!conversation?.id) {\r\n return [];\r\n }\r\n const userId = this.currentUser?.userId || '';\r\n return applyLocalMessageState(conversation.id, readLocalMessages(userId)[conversation.id] || [], userId);\r\n }\r\n\r\n async fetchRemoteMessages(conversation, limit = 50, options = {}) {\r\n if (!conversation?.id || !this.currentUser?.userId) {\r\n return [];\r\n }\r\n const response = await axios.post(appConfig.messagesApi, {\r\n userId: this.currentUser.userId,\r\n target: conversation.target || conversation.raw?.target || conversation.id.replace(/^single-/, ''),\r\n limit,\r\n conversationType: conversation.type || conversation.raw?.type || 0,\r\n markRead: Boolean(options.markRead)\r\n }, { withCredentials: false });\r\n\r\n if (response.data?.code !== 0) {\r\n throw new Error(response.data?.message || '消息同步失败');\r\n }\r\n\r\n const messages = (response.data?.result?.messages || []).map(message => ({\r\n ...message,\r\n displayTimestamp: message.displayTimestamp || message.timestamp,\r\n conversationId: conversation.id\r\n }));\r\n const userId = this.currentUser?.userId || '';\r\n const messagesByConversation = readLocalMessages(userId);\r\n messagesByConversation[conversation.id] = mergeSyncedMessages(messagesByConversation[conversation.id] || [], messages, conversation.id, userId);\r\n writeLocalMessages(messagesByConversation, userId);\r\n return messagesByConversation[conversation.id];\r\n }\r\n\r\n hideMessages(conversation, messages) {\r\n const items = Array.isArray(messages) ? messages : [messages];\r\n if (!conversation?.id || !items.length) {\r\n return;\r\n }\r\n const userId = this.currentUser?.userId || '';\r\n storeMessageKeys(LOCAL_HIDDEN_MESSAGE_STORAGE_KEY, conversation.id, items, userId);\r\n const messagesByConversation = readLocalMessages(userId);\r\n messagesByConversation[conversation.id] = applyLocalMessageState(\r\n conversation.id,\r\n messagesByConversation[conversation.id] || [],\r\n userId\r\n );\r\n writeLocalMessages(messagesByConversation, userId);\r\n }\r\n\r\n markMessagesRecalled(conversation, messages) {\r\n const items = Array.isArray(messages) ? messages : [messages];\r\n if (!conversation?.id || !items.length) {\r\n return;\r\n }\r\n const userId = this.currentUser?.userId || '';\r\n storeMessageKeys(LOCAL_RECALLED_MESSAGE_STORAGE_KEY, conversation.id, items, userId);\r\n const messagesByConversation = readLocalMessages(userId);\r\n messagesByConversation[conversation.id] = applyLocalMessageState(\r\n conversation.id,\r\n messagesByConversation[conversation.id] || [],\r\n userId\r\n );\r\n writeLocalMessages(messagesByConversation, userId);\r\n }\r\n\r\n async recallMessage(conversation, message) {\r\n const messageUid = String(message?.messageUid || message?.id || '').trim();\r\n if (!/^\\d+$/.test(messageUid)) {\r\n throw new Error('缺少可撤回的消息ID');\r\n }\r\n\r\n const response = await axios.post(appConfig.messageActionApi, {\r\n action: 'recall',\r\n userId: this.currentUser?.userId || message.from || '',\r\n messageUid\r\n }, { withCredentials: false });\r\n\r\n if (response.data?.code !== 0) {\r\n throw new Error(response.data?.message || '撤回失败');\r\n }\r\n this.markMessagesRecalled(conversation, [message]);\r\n return response.data.result;\r\n }\r\n\r\n async editMessage(conversation, message, text) {\r\n const messageUid = String(message?.messageUid || message?.id || '').trim();\r\n if (!/^\\d+$/.test(messageUid)) {\r\n throw new Error('缺少可编辑的消息ID');\r\n }\r\n const nextText = String(text || '').trim();\r\n if (!nextText) {\r\n throw new Error('编辑内容不能为空');\r\n }\r\n\r\n const response = await axios.post(appConfig.messageActionApi, {\r\n action: 'edit',\r\n userId: this.currentUser?.userId || message.from || '',\r\n messageUid,\r\n text: nextText\r\n }, { withCredentials: false });\r\n\r\n if (response.data?.code !== 0) {\r\n throw new Error(response.data?.message || '编辑失败');\r\n }\r\n\r\n const result = response.data?.result?.message || {};\r\n const editedMessage = {\r\n ...message,\r\n text: result.text || nextText,\r\n status: 'delivered',\r\n deliveryStatus: 'delivered',\r\n editedAt: Number(result.editedAt || Date.now()),\r\n activityTimestamp: Number(result.editedAt || Date.now())\r\n };\r\n saveLocalMessage(editedMessage, this.currentUser?.userId || '');\r\n this.emit('messageStatusChanged', editedMessage);\r\n return editedMessage;\r\n }\r\n\r\n async getMessageDetail(conversation, message) {\r\n const messageUid = String(message?.messageUid || message?.id || '').trim();\r\n if (!messageUid) {\r\n throw new Error('缺少消息ID');\r\n }\r\n const response = await axios.post(appConfig.messageDetailApi, {\r\n userId: this.currentUser?.userId || '',\r\n target: conversation?.target || conversation?.raw?.target || conversation?.id?.replace(/^single-/, '') || '',\r\n conversationType: conversation?.type || conversation?.raw?.type || 'single',\r\n messageUid\r\n }, { withCredentials: false });\r\n\r\n if (response.data?.code !== 0) {\r\n throw new Error(response.data?.message || '消息详情加载失败');\r\n }\r\n\r\n return response.data?.result?.detail || null;\r\n }\r\n\r\n async sendTextMessage(conversation, text, initialMessage = null) {\r\n const senderRole = conversation?.role || conversation?.raw?.role || '';\r\n const message = initialMessage || {\r\n id: randomId(),\r\n conversationId: conversation.id,\r\n from: this.currentUser?.userId || 'me',\r\n senderRole,\r\n direction: 'out',\r\n type: 'text',\r\n text,\r\n status: 'sending',\r\n deliveryStatus: 'sending',\r\n timestamp: Date.now()\r\n };\r\n saveLocalMessage(message, this.currentUser?.userId || '');\r\n if (!initialMessage) {\r\n this.emit('messageSent', message);\r\n }\r\n\r\n try {\r\n const response = await axios.post(appConfig.sendMessageApi, {\r\n sender: this.currentUser?.userId || '',\r\n target: conversation.target || conversation.raw?.target || conversation.id.replace(/^single-/, ''),\r\n conversationType: conversation.type || conversation.raw?.type || 0,\r\n text\r\n }, { withCredentials: false });\r\n\r\n if (response.data?.code !== 0) {\r\n throw new Error(response.data?.message || '发送失败');\r\n }\r\n\r\n const sentMessage = {\r\n ...message,\r\n status: 'sent',\r\n deliveryStatus: 'sent',\r\n messageUid: response.data?.result?.messageUid ? String(response.data.result.messageUid) : '',\r\n senderRole: response.data?.result?.senderRole || message.senderRole || '',\r\n serverTimestamp: Number(response.data?.result?.timestamp || 0) || null,\r\n timestamp: message.timestamp,\r\n displayTimestamp: message.displayTimestamp || message.timestamp\r\n };\r\n saveLocalMessage(sentMessage, this.currentUser?.userId || '');\r\n this.emit('messageStatusChanged', sentMessage);\r\n return sentMessage;\r\n } catch (error) {\r\n const failedMessage = {\r\n ...message,\r\n status: 'failed',\r\n deliveryStatus: '',\r\n error: error.response?.data?.message || error.message || '发送失败'\r\n };\r\n saveLocalMessage(failedMessage, this.currentUser?.userId || '');\r\n this.emit('messageStatusChanged', failedMessage);\r\n return failedMessage;\r\n }\r\n }\r\n\r\n async sendAttachmentMessage(conversation, { type, file, text = '' }, initialMessage = null) {\r\n const senderRole = conversation?.role || conversation?.raw?.role || '';\r\n const message = initialMessage || {\r\n id: randomId(),\r\n conversationId: conversation.id,\r\n from: this.currentUser?.userId || 'me',\r\n senderRole,\r\n direction: 'out',\r\n type,\r\n text,\r\n attachment: {\r\n type,\r\n name: file?.name || (type === 'image' ? '图片' : '文件'),\r\n mime: file?.type || '',\r\n size: file?.size || 0,\r\n url: type === 'image' && file ? URL.createObjectURL(file) : ''\r\n },\r\n status: 'sending',\r\n deliveryStatus: 'sending',\r\n timestamp: Date.now()\r\n };\r\n saveLocalMessage(message, this.currentUser?.userId || '');\r\n if (!initialMessage) {\r\n this.emit('messageSent', message);\r\n }\r\n\r\n try {\r\n const formData = new FormData();\r\n formData.append('sender', this.currentUser?.userId || '');\r\n formData.append('target', conversation.target || conversation.raw?.target || conversation.id.replace(/^single-/, ''));\r\n formData.append('conversationType', conversation.type || conversation.raw?.type || 0);\r\n formData.append('messageType', type);\r\n formData.append('text', text || '');\r\n formData.append('attachment', file);\r\n\r\n const response = await axios.post(appConfig.sendMessageApi, formData, {\r\n withCredentials: false\r\n });\r\n\r\n if (response.data?.code !== 0) {\r\n throw new Error(response.data?.message || '发送失败');\r\n }\r\n\r\n const sentMessage = {\r\n ...message,\r\n status: 'sent',\r\n deliveryStatus: 'sent',\r\n messageUid: response.data?.result?.messageUid ? String(response.data.result.messageUid) : '',\r\n senderRole: response.data?.result?.senderRole || message.senderRole || '',\r\n attachment: response.data?.result?.attachment || message.attachment,\r\n serverTimestamp: Number(response.data?.result?.timestamp || 0) || null,\r\n timestamp: message.timestamp,\r\n displayTimestamp: message.displayTimestamp || message.timestamp\r\n };\r\n saveLocalMessage(sentMessage, this.currentUser?.userId || '');\r\n this.emit('messageStatusChanged', sentMessage);\r\n return sentMessage;\r\n } catch (error) {\r\n const failedMessage = {\r\n ...message,\r\n status: 'failed',\r\n deliveryStatus: '',\r\n error: error.response?.data?.message || error.message || '发送失败'\r\n };\r\n saveLocalMessage(failedMessage, this.currentUser?.userId || '');\r\n this.emit('messageStatusChanged', failedMessage);\r\n return failedMessage;\r\n }\r\n }\r\n\r\n getContactList() {\r\n return [];\r\n }\r\n\r\n async getServerContactList(userId) {\r\n if (!userId) {\r\n return [];\r\n }\r\n const response = await axios.post(appConfig.contactsApi, { userId }, {\r\n withCredentials: false,\r\n headers: this.getAppAuthHeaders(appConfig.contactsApi)\r\n });\r\n if (response.data?.code !== 0) {\r\n throw new Error(response.data?.message || '联系人加载失败');\r\n }\r\n return (response.data?.result?.contacts || []).map(item => {\r\n return this.mapContact(item.uid, { alias: item.alias }, {\r\n uid: item.uid,\r\n displayName: item.displayName,\r\n name: item.name,\r\n mobile: item.mobile,\r\n portrait: item.portrait,\r\n address: item.address,\r\n friendAlias: item.alias\r\n });\r\n }).sort((a, b) => a.title.localeCompare(b.title, 'zh-CN'));\r\n }\r\n\r\n async addContact(userId, keyword, reason = '') {\r\n const response = await axios.post(appConfig.addContactApi, {\r\n userId,\r\n keyword,\r\n reason\r\n }, { withCredentials: false });\r\n\r\n if (response.data?.code !== 0) {\r\n throw new Error(response.data?.message || '添加联系人失败');\r\n }\r\n\r\n const item = response.data?.result?.contact;\r\n if (!item?.uid) {\r\n return null;\r\n }\r\n\r\n return this.mapContact(item.uid, { alias: item.alias }, {\r\n uid: item.uid,\r\n displayName: item.displayName,\r\n name: item.name,\r\n mobile: item.mobile,\r\n portrait: item.portrait,\r\n address: item.address,\r\n friendAlias: item.alias\r\n });\r\n }\r\n\r\n normalizeConversation(conversation) {\r\n const title = getTitle(conversation?.title, conversation?.target || '');\r\n return {\r\n ...conversation,\r\n id: conversation?.id || `${conversation?.type === 'single' ? 'single' : 'group'}-${conversation?.target}`,\r\n target: conversation?.target || conversation?.raw?.target || '',\r\n title,\r\n subtitle: conversation?.subtitle || '',\r\n unread: Number(conversation?.unread || 0),\r\n portrait: conversation?.portrait || '',\r\n avatarText: conversation?.avatarText || title.slice(0, 1).toUpperCase(),\r\n updatedAt: Number(conversation?.updatedAt || Date.now()),\r\n raw: conversation?.raw || {\r\n type: conversation?.type === 'single' ? 'single' : 'group',\r\n target: conversation?.target || '',\r\n line: 0\r\n }\r\n };\r\n }\r\n\r\n async createGroup(userId, { name, memberIds = [], announcement = '', welcomeText = '', channel = false }) {\r\n const response = await axios.post(appConfig.conversationActionsApi, {\r\n action: channel ? 'create_channel' : 'create_group',\r\n userId,\r\n name,\r\n memberIds,\r\n announcement,\r\n welcomeText\r\n }, { withCredentials: false });\r\n\r\n if (response.data?.code !== 0) {\r\n throw new Error(response.data?.message || (channel ? '创建公告群失败' : '创建群组失败'));\r\n }\r\n\r\n return this.normalizeConversation(response.data?.result?.conversation || {});\r\n }\r\n\r\n async joinGroup(userId, groupKey) {\r\n const response = await axios.post(appConfig.conversationActionsApi, {\r\n action: 'join_group',\r\n userId,\r\n groupKey\r\n }, { withCredentials: false });\r\n\r\n if (response.data?.code !== 0) {\r\n throw new Error(response.data?.message || '加入群组失败');\r\n }\r\n\r\n return this.normalizeConversation(response.data?.result?.conversation || {});\r\n }\r\n\r\n async broadcastMessage(userId, targetIds, text) {\r\n const response = await axios.post(appConfig.conversationActionsApi, {\r\n action: 'broadcast',\r\n userId,\r\n targetIds,\r\n text\r\n }, { withCredentials: false });\r\n\r\n if (response.data?.code !== 0) {\r\n throw new Error(response.data?.message || '群发失败');\r\n }\r\n\r\n return response.data?.result || {};\r\n }\r\n\r\n async getGroupDetail(userId, groupId) {\r\n const response = await axios.post(appConfig.conversationActionsApi, {\r\n action: 'group_detail',\r\n userId,\r\n groupId\r\n }, { withCredentials: false });\r\n\r\n if (response.data?.code !== 0) {\r\n throw new Error(response.data?.message || '群资料加载失败');\r\n }\r\n\r\n return response.data?.result?.group || null;\r\n }\r\n\r\n async getGroupMembers(userId, groupId) {\r\n const response = await axios.post(appConfig.conversationActionsApi, {\r\n action: 'group_members',\r\n userId,\r\n groupId\r\n }, { withCredentials: false });\r\n\r\n if (response.data?.code !== 0) {\r\n throw new Error(response.data?.message || '群员加载失败');\r\n }\r\n\r\n return response.data?.result?.members || [];\r\n }\r\n\r\n async updateGroupAlias(userId, groupId, alias) {\r\n const response = await axios.post(appConfig.conversationActionsApi, {\r\n action: 'update_group_alias',\r\n userId,\r\n groupId,\r\n alias\r\n }, { withCredentials: false });\r\n\r\n if (response.data?.code !== 0) {\r\n throw new Error(response.data?.message || '本群昵称保存失败');\r\n }\r\n\r\n return response.data?.result?.alias || '';\r\n }\r\n\r\n async leaveGroup(userId, groupId) {\r\n const response = await axios.post(appConfig.conversationActionsApi, {\r\n action: 'leave_group',\r\n userId,\r\n groupId\r\n }, { withCredentials: false });\r\n\r\n if (response.data?.code !== 0) {\r\n throw new Error(response.data?.message || '退出群组失败');\r\n }\r\n\r\n return response.data?.result || {};\r\n }\r\n\r\n async dismissGroup(userId, groupId) {\r\n const response = await axios.post(appConfig.conversationActionsApi, {\r\n action: 'dismiss_group',\r\n userId,\r\n groupId\r\n }, { withCredentials: false });\r\n\r\n if (response.data?.code !== 0) {\r\n throw new Error(response.data?.message || '解散群组失败');\r\n }\r\n\r\n return response.data?.result || {};\r\n }\r\n\r\n async transferGroup(userId, groupId, targetUserId) {\r\n const response = await axios.post(appConfig.conversationActionsApi, {\r\n action: 'transfer_group',\r\n userId,\r\n groupId,\r\n targetUserId\r\n }, { withCredentials: false });\r\n\r\n if (response.data?.code !== 0) {\r\n throw new Error(response.data?.message || '转让群组失败');\r\n }\r\n\r\n return response.data?.result || {};\r\n }\r\n\r\n async setGroupAdmins(userId, groupId, adminIds) {\r\n const response = await axios.post(appConfig.conversationActionsApi, {\r\n action: 'set_group_admins',\r\n userId,\r\n groupId,\r\n adminIds\r\n }, { withCredentials: false });\r\n\r\n if (response.data?.code !== 0) {\r\n throw new Error(response.data?.message || '管理员设置失败');\r\n }\r\n\r\n return response.data?.result || {};\r\n }\r\n\r\n async updateGroupProfile(userId, groupId, { name, announcement, welcomeText }) {\r\n const response = await axios.post(appConfig.conversationActionsApi, {\r\n action: 'update_group_profile',\r\n userId,\r\n groupId,\r\n name,\r\n announcement,\r\n welcomeText\r\n }, { withCredentials: false });\r\n\r\n if (response.data?.code !== 0) {\r\n throw new Error(response.data?.message || '群资料保存失败');\r\n }\r\n\r\n return response.data?.result || {};\r\n }\r\n\r\n async kickGroupMembers(userId, groupId, targetIds) {\r\n const response = await axios.post(appConfig.conversationActionsApi, {\r\n action: 'kick_group_members',\r\n userId,\r\n groupId,\r\n targetIds\r\n }, { withCredentials: false });\r\n\r\n if (response.data?.code !== 0) {\r\n throw new Error(response.data?.message || '踢人失败');\r\n }\r\n\r\n return response.data?.result || {};\r\n }\r\n\r\n async updateGroupMuteSetting(userId, groupId, setting) {\r\n const response = await axios.post(appConfig.conversationActionsApi, {\r\n action: 'update_mute_setting',\r\n userId,\r\n groupId,\r\n ...setting\r\n }, { withCredentials: false });\r\n\r\n if (response.data?.code !== 0) {\r\n throw new Error(response.data?.message || '禁言设置保存失败');\r\n }\r\n\r\n return response.data?.result?.muteSetting || null;\r\n }\r\n\r\n async updateContactAlias(userId, targetId, alias) {\r\n const response = await axios.post(appConfig.updateAliasApi, {\r\n userId,\r\n targetId,\r\n alias\r\n }, {\r\n withCredentials: false,\r\n headers: this.getAppAuthHeaders(appConfig.updateAliasApi)\r\n });\r\n\r\n if (response.data?.code !== 0) {\r\n throw new Error(response.data?.message || '备注名修改失败');\r\n }\r\n\r\n return response.data?.result?.contact?.alias || '';\r\n }\r\n\r\n async getProfile(userId) {\r\n const response = await axios.post(appConfig.profileApi, {\r\n action: 'get',\r\n userId\r\n }, { withCredentials: false });\r\n\r\n if (response.data?.code !== 0) {\r\n throw new Error(response.data?.message || '用户资料加载失败');\r\n }\r\n\r\n return response.data?.result?.profile || null;\r\n }\r\n\r\n async updateProfile(userId, { displayName, portraitFile }) {\r\n const formData = new FormData();\r\n formData.append('action', 'update');\r\n formData.append('userId', userId);\r\n formData.append('displayName', displayName);\r\n if (portraitFile) {\r\n formData.append('portraitFile', portraitFile);\r\n }\r\n\r\n const response = await axios.post(appConfig.profileApi, formData, {\r\n withCredentials: false,\r\n headers: this.getAppAuthHeaders(appConfig.appServer)\r\n });\r\n\r\n if (response.data?.code !== 0) {\r\n throw new Error(response.data?.message || '用户资料保存失败');\r\n }\r\n\r\n return response.data?.result?.profile || null;\r\n }\r\n\r\n async changePassword({ oldPassword, newPassword }) {\r\n const response = await axios.post(appConfig.changePasswordApi, {\r\n oldPassword,\r\n newPassword\r\n }, {\r\n withCredentials: false,\r\n headers: this.getAppAuthHeaders(appConfig.changePasswordApi)\r\n });\r\n\r\n if (response.data?.code !== 0) {\r\n throw new Error(response.data?.message || response.data?.msg || '密码修改失败');\r\n }\r\n\r\n return true;\r\n }\r\n\r\n async resetPasswordWithCode({ mobile, resetCode, newPassword }) {\r\n const response = await axios.post(appConfig.resetPasswordApi, {\r\n mobile,\r\n resetCode,\r\n newPassword\r\n }, { withCredentials: false });\r\n\r\n if (response.data?.code !== 0) {\r\n throw new Error(response.data?.message || response.data?.msg || '密码设置失败');\r\n }\r\n\r\n return true;\r\n }\r\n\r\n refreshContactList() {\r\n const contacts = this.getContactList();\r\n this.emit('contactsChanged', contacts);\r\n return contacts;\r\n }\r\n\r\n createSingleConversation(userId, userInfo = null) {\r\n const title = getTitle(userInfo?.title || userInfo?.displayName || userInfo?.name, userId);\r\n return {\r\n id: `single-${userId}`,\r\n type: 'single',\r\n target: userId,\r\n title,\r\n subtitle: '',\r\n unread: 0,\r\n portrait: userInfo?.portrait || '',\r\n avatarText: title.slice(0, 1).toUpperCase(),\r\n updatedAt: Date.now(),\r\n raw: { type: 'single', target: userId, line: 0 }\r\n };\r\n }\r\n\r\n mapContact(userId, friend, userInfo) {\r\n const title = getTitle(userInfo?.friendAlias || friend?.alias || userInfo?.displayName || userInfo?.name, userId);\r\n return {\r\n id: userId,\r\n uid: userId,\r\n title,\r\n subtitle: userInfo?.mobile || userInfo?.name || userId,\r\n portrait: userInfo?.portrait || '',\r\n address: userInfo?.address || '',\r\n avatarText: title.slice(0, 1).toUpperCase(),\r\n alias: friend?.alias || userInfo?.friendAlias || '',\r\n rawFriend: friend,\r\n rawUser: userInfo\r\n };\r\n }\r\n\r\n on(eventName, handler) {\r\n if (!this.listeners.has(eventName)) {\r\n this.listeners.set(eventName, new Set());\r\n }\r\n this.listeners.get(eventName).add(handler);\r\n return () => this.off(eventName, handler);\r\n }\r\n\r\n off(eventName, handler) {\r\n this.listeners.get(eventName)?.delete(handler);\r\n }\r\n\r\n emit(eventName, payload) {\r\n this.listeners.get(eventName)?.forEach(handler => handler(payload));\r\n }\r\n\r\n persistAppToken(url, response) {\r\n const appToken = response.headers?.authtoken || response.headers?.authToken;\r\n if (appToken) {\r\n localStorage.setItem(appConfig.authTokenStoragePrefix + this.getUrlHost(url), appToken);\r\n }\r\n }\r\n\r\n getAppAuthHeaders(url) {\r\n const host = this.getUrlHost(url);\r\n if (!host) {\r\n return {};\r\n }\r\n const token = localStorage.getItem(appConfig.authTokenStoragePrefix + host);\r\n return token ? { authToken: token } : {};\r\n }\r\n\r\n getUrlHost(url) {\r\n try {\r\n return new URL(url, window.location.origin).host;\r\n } catch (error) {\r\n return '';\r\n }\r\n }\r\n}\r\n\r\nexport const imClient = new HttpImClient();\r\n","import { appConfig } from '@/config';\nimport { HttpImClient } from './httpImClient';\nimport { LongConnectionImClient } from './longConnectionImClient';\nimport { DocumentedHttpImClient } from './documentedHttpImClient';\n\r\nclass UnsupportedWildfireImClient {\r\n constructor() {\r\n throw new Error('当前本地社区版 Web 客户端未启用 WildFire Web SDK');\r\n }\r\n}\r\n\r\nexport const imClient = appConfig.sdkMode === 'wildfire'\n ? new UnsupportedWildfireImClient()\n : appConfig.sdkMode === 'custom-ws' || appConfig.sdkMode === 'long'\n ? new LongConnectionImClient()\n : new DocumentedHttpImClient();\n\nexport {\n DocumentedHttpImClient,\n HttpImClient,\n LongConnectionImClient,\n UnsupportedWildfireImClient as WildfireImClient\n};\n","import { appConfig } from '@/config';\r\nimport { HttpImClient } from './httpImClient';\r\n\r\nconst HEARTBEAT_INTERVAL_MS = 25000;\r\nconst RECONNECT_MIN_MS = 1000;\r\nconst RECONNECT_MAX_MS = 15000;\r\n\r\nfunction buildWsUrl() {\r\n if (appConfig.wsUrl) {\r\n return appConfig.wsUrl;\r\n }\r\n const protocol = appConfig.useWss ? 'wss' : 'ws';\r\n const host = appConfig.imServerHost || window.location.hostname;\r\n const port = appConfig.imRoutePort ? `:${appConfig.imRoutePort}` : '';\r\n const path = appConfig.wsPath?.startsWith('/') ? appConfig.wsPath : `/${appConfig.wsPath || 'ws'}`;\r\n return `${protocol}://${host}${port}${path}`;\r\n}\r\n\r\nfunction normalizeConversationId(payload) {\r\n const target = payload.target || payload.to || payload.receiver || payload.conversationTarget || payload.from || payload.sender;\r\n return payload.conversationId || (target ? `single-${target}` : '');\r\n}\r\n\r\nfunction normalizeIncomingMessage(payload, currentUserId) {\r\n const item = payload.message || payload;\r\n const from = item.from || item.sender || item.fromUser || '';\r\n const target = item.target || item.to || item.receiver || '';\r\n const direction = from && String(from) === String(currentUserId) ? 'out' : 'in';\r\n const conversationId = item.conversationId || normalizeConversationId({\r\n ...item,\r\n target: direction === 'out' ? target : from\r\n });\r\n\r\n if (!conversationId) {\r\n return null;\r\n }\r\n\r\n const status = item.status || item.deliveryStatus || 'sent';\r\n\r\n return {\r\n id: item.id || item.messageId || item.messageUid || `${Date.now()}${Math.random().toString(16).slice(2)}`,\r\n messageUid: item.messageUid || item.uid || item.messageId || '',\r\n conversationId,\r\n from,\r\n target,\r\n direction,\r\n type: item.type || 'text',\r\n text: item.text || item.content || item.searchableContent || '',\r\n status,\r\n deliveryStatus: item.deliveryStatus || (direction === 'out' ? status : ''),\r\n timestamp: Number(item.timestamp || item.serverTimestamp || Date.now()),\r\n displayTimestamp: Number(item.displayTimestamp || item.timestamp || item.serverTimestamp || Date.now()),\r\n raw: item\r\n };\r\n}\r\n\r\nexport class LongConnectionImClient extends HttpImClient {\r\n constructor() {\r\n super();\r\n this.socket = null;\r\n this.wsUrl = buildWsUrl();\r\n this.heartbeatTimer = null;\r\n this.reconnectTimer = null;\r\n this.reconnectAttempts = 0;\r\n this.manualClose = false;\r\n }\r\n\r\n getSdkVersion() {\r\n return 'JC-LC/0.1';\r\n }\r\n\r\n shouldUseMessagePolling() {\r\n return !this.connected;\r\n }\r\n\r\n async ensureSdk() {\r\n if (typeof WebSocket === 'undefined') {\r\n throw new Error('当前浏览器不支持 WebSocket 长连接');\r\n }\r\n return true;\r\n }\r\n\r\n connect(userId, token) {\r\n this.currentUser = { userId, token };\r\n this.manualClose = false;\r\n this.openSocket();\r\n }\r\n\r\n disconnect() {\r\n this.manualClose = true;\r\n this.clearReconnectTimer();\r\n this.clearHeartbeatTimer();\r\n if (this.socket) {\r\n this.socket.close();\r\n this.socket = null;\r\n }\r\n super.disconnect();\r\n }\r\n\r\n openSocket() {\r\n if (this.socket?.readyState === WebSocket.OPEN) {\r\n this.sendAuthFrame();\r\n return;\r\n }\r\n if (this.socket?.readyState === WebSocket.CONNECTING) {\r\n return;\r\n }\r\n\r\n this.emit('connectionStatusChanged', { connected: false, statusText: '长连接连接中' });\r\n\r\n try {\r\n this.socket = new WebSocket(this.wsUrl);\r\n this.socket.binaryType = 'arraybuffer';\r\n } catch (error) {\r\n this.emit('connectionStatusChanged', { connected: false, statusText: '长连接创建失败,使用HTTP补偿' });\r\n this.scheduleReconnect();\r\n return;\r\n }\r\n\r\n this.socket.onopen = () => {\r\n this.connected = true;\r\n this.reconnectAttempts = 0;\r\n this.emit('connectionStatusChanged', { connected: true, statusText: '长连接已连接' });\r\n this.sendAuthFrame();\r\n this.startHeartbeat();\r\n this.refreshConversationList();\r\n };\r\n\r\n this.socket.onmessage = event => {\r\n this.handleFrame(event.data);\r\n };\r\n\r\n this.socket.onerror = () => {\r\n this.emit('connectionStatusChanged', { connected: false, statusText: '长连接异常,使用HTTP补偿' });\r\n };\r\n\r\n this.socket.onclose = () => {\r\n this.connected = false;\r\n this.clearHeartbeatTimer();\r\n this.socket = null;\r\n if (this.manualClose) {\r\n return;\r\n }\r\n this.emit('connectionStatusChanged', { connected: false, statusText: '长连接重连中' });\r\n this.scheduleReconnect();\r\n };\r\n }\r\n\r\n sendAuthFrame() {\r\n this.sendFrame({\r\n type: 'auth',\r\n userId: this.currentUser?.userId || '',\r\n token: this.currentUser?.token || '',\r\n clientId: this.getClientId(),\r\n platform: appConfig.platform,\r\n timestamp: Date.now()\r\n });\r\n }\r\n\r\n sendFrame(frame) {\r\n if (this.socket?.readyState !== WebSocket.OPEN) {\r\n return false;\r\n }\r\n this.socket.send(JSON.stringify(frame));\r\n return true;\r\n }\r\n\r\n handleFrame(rawData) {\r\n if (rawData instanceof ArrayBuffer) {\r\n this.emit('rawFrameReceived', rawData);\r\n return;\r\n }\r\n\r\n let payload;\r\n try {\r\n payload = typeof rawData === 'string' ? JSON.parse(rawData) : rawData;\r\n } catch (error) {\r\n return;\r\n }\r\n\r\n const type = payload.type || payload.event || payload.cmd || '';\r\n if (type === 'pong' || type === 'auth_ok' || type === 'connected') {\r\n return;\r\n }\r\n if (type === 'contactsChanged') {\r\n this.emit('contactsChanged', payload.contacts || []);\r\n return;\r\n }\r\n if (type === 'conversationsChanged') {\r\n this.emit('conversationsChanged', payload.conversations || []);\r\n return;\r\n }\r\n if (type === 'messages' && Array.isArray(payload.messages)) {\r\n payload.messages\r\n .map(message => normalizeIncomingMessage(message, this.currentUser?.userId))\r\n .filter(Boolean)\r\n .forEach(message => this.emit('messageReceived', message));\r\n return;\r\n }\r\n\r\n const message = normalizeIncomingMessage(payload, this.currentUser?.userId);\r\n if (message) {\r\n this.emit(message.direction === 'out' ? 'messageStatusChanged' : 'messageReceived', message);\r\n }\r\n }\r\n\r\n startHeartbeat() {\r\n this.clearHeartbeatTimer();\r\n this.heartbeatTimer = window.setInterval(() => {\r\n this.sendFrame({ type: 'ping', timestamp: Date.now() });\r\n }, HEARTBEAT_INTERVAL_MS);\r\n }\r\n\r\n clearHeartbeatTimer() {\r\n if (this.heartbeatTimer) {\r\n window.clearInterval(this.heartbeatTimer);\r\n this.heartbeatTimer = null;\r\n }\r\n }\r\n\r\n scheduleReconnect() {\r\n this.clearReconnectTimer();\r\n const delay = Math.min(RECONNECT_MAX_MS, RECONNECT_MIN_MS * 2 ** this.reconnectAttempts);\r\n this.reconnectAttempts += 1;\r\n this.reconnectTimer = window.setTimeout(() => {\r\n this.openSocket();\r\n }, delay);\r\n }\r\n\r\n clearReconnectTimer() {\r\n if (this.reconnectTimer) {\r\n window.clearTimeout(this.reconnectTimer);\r\n this.reconnectTimer = null;\r\n }\r\n }\r\n}\r\n","import { defineStore } from 'pinia';\nimport { imClient } from '@/services/imClient';\nimport { appConfig } from '@/config';\n\nconst STORAGE_KEY = 'qmt-web-im-session';\n\nfunction normalizeStoredPortrait(portrait) {\n return portrait || '';\n}\n\nfunction nextProfilePortrait(profilePortrait, currentPortrait) {\n return normalizeStoredPortrait(profilePortrait) || normalizeStoredPortrait(currentPortrait);\n}\n\nfunction inferAccountType(account) {\n return String(account || '').includes('@') ? 2 : 1;\n}\n\nexport const useSessionStore = defineStore('session', {\n state: () => ({\n isAuthenticated: false,\n connecting: false,\n connected: false,\n connectionStatusText: '未连接',\n userId: '',\n token: '',\n displayName: '',\n portrait: '',\n error: '',\n sdkVersion: '',\n eventsBound: false\n }),\n actions: {\n restore() {\n const raw = localStorage.getItem(STORAGE_KEY);\n if (!raw) {\n return;\n }\n\n try {\n const session = JSON.parse(raw);\n if (session.appServer !== appConfig.appServer) {\n localStorage.removeItem(STORAGE_KEY);\n return;\n }\n\n if (session.userId && session.token) {\n if (!session.clientId) {\n localStorage.removeItem(STORAGE_KEY);\n return;\n }\n this.userId = session.userId;\n this.token = session.token;\n this.displayName = session.displayName || session.userId;\n this.portrait = normalizeStoredPortrait(session.portrait);\n this.isAuthenticated = true;\n imClient.restoreClientId(session.clientId);\n this.connect();\n this.refreshProfile();\n }\n } catch (error) {\n localStorage.removeItem(STORAGE_KEY);\n }\n },\n\n async loginWithPassword(account, password, type = inferAccountType(account)) {\n this.error = '';\n this.connecting = true;\n try {\n const result = await imClient.loginWithPassword(account, password, type);\n this.applyLoginResult(result);\n await this.connect();\n } catch (error) {\n this.error = error.message || '登录失败';\n } finally {\n this.connecting = false;\n }\n },\n\n async loginWithCode(account, code) {\n this.error = '';\n this.connecting = true;\n try {\n const result = await imClient.loginWithCode(account, code);\n this.applyLoginResult(result);\n await this.connect();\n } catch (error) {\n this.error = error.message || '登录失败';\n } finally {\n this.connecting = false;\n }\n },\n\n async completePcSessionLogin(result) {\n this.error = '';\n this.connecting = true;\n try {\n this.applyLoginResult(result);\n await this.connect();\n } catch (error) {\n this.error = error.message || '扫码登录失败';\n } finally {\n this.connecting = false;\n }\n },\n\n async register(payload) {\n this.error = '';\n this.connecting = true;\n try {\n const result = await imClient.register(payload);\n this.applyLoginResult(result);\n await this.connect();\n return result;\n } catch (error) {\n this.error = error.message || '注册失败';\n return null;\n } finally {\n this.connecting = false;\n }\n },\n\n applyLoginResult(result) {\n this.userId = result.userId || result.uid || result.id || '';\n this.token = result.token || result.imToken || '';\n this.displayName = result.displayName || result.userName || result.name || this.userId;\n this.portrait = normalizeStoredPortrait(result.portrait);\n this.isAuthenticated = Boolean(this.userId && this.token);\n this.persistSession();\n },\n\n persistSession() {\n localStorage.setItem(STORAGE_KEY, JSON.stringify({\n userId: this.userId,\n token: this.token,\n displayName: this.displayName,\n portrait: this.portrait,\n clientId: imClient.getClientId(),\n appServer: appConfig.appServer\n }));\n },\n\n applyProfile(profile) {\n if (!profile) {\n return;\n }\n this.userId = profile.userId || profile.uid || this.userId;\n this.displayName = profile.displayName || profile.name || this.displayName || this.userId;\n this.portrait = nextProfilePortrait(profile.portrait, this.portrait);\n this.persistSession();\n },\n\n async refreshProfile() {\n if (!this.userId) {\n return null;\n }\n try {\n const profile = await imClient.getProfile(this.userId);\n this.applyProfile(profile);\n return profile;\n } catch (error) {\n return null;\n }\n },\n\n async saveProfile({ displayName, portraitFile }) {\n if (!this.userId) {\n throw new Error('请先登录');\n }\n const profile = await imClient.updateProfile(this.userId, {\n displayName,\n portraitFile\n });\n this.applyProfile(profile);\n return profile;\n },\n\n async changePassword({ oldPassword, newPassword }) {\n if (!this.userId) {\n throw new Error('请先登录');\n }\n await imClient.changePassword({ oldPassword, newPassword });\n await this.logout();\n return true;\n },\n\n async resetPasswordWithCode({ mobile, resetCode, newPassword }) {\n await imClient.resetPasswordWithCode({ mobile, resetCode, newPassword });\n return true;\n },\n\n async connect() {\n if (!this.userId || !this.token) {\n return;\n }\n await imClient.ensureSdk();\n this.sdkVersion = imClient.getSdkVersion();\n this.bindImEvents();\n await imClient.connect(this.userId, this.token);\n },\n\n bindImEvents() {\n if (this.eventsBound) {\n return;\n }\n this.eventsBound = true;\n imClient.on('connectionStatusChanged', status => {\n this.connected = status.connected;\n this.connectionStatusText = status.statusText || (status.connected ? '已连接' : '未连接');\n });\n },\n\n async logout() {\n try {\n await imClient.logout?.();\n } catch (error) {\n this.error = error.message || '退出登录失败';\n } finally {\n imClient.disconnect();\n localStorage.removeItem(STORAGE_KEY);\n this.$reset();\n }\n }\n }\n});\n","import script from \"./App.vue?vue&type=script&setup=true&lang=js\"\nexport * from \"./App.vue?vue&type=script&setup=true&lang=js\"\n\nconst __exports__ = script;\n\nexport default __exports__","import script from \"./DocumentedShell.vue?vue&type=script&setup=true&lang=js\"\nexport * from \"./DocumentedShell.vue?vue&type=script&setup=true&lang=js\"\n\nconst __exports__ = script;\n\nexport default __exports__","import script from \"./LoginView.vue?vue&type=script&setup=true&lang=js\"\nexport * from \"./LoginView.vue?vue&type=script&setup=true&lang=js\"\n\nconst __exports__ = script;\n\nexport default __exports__","export { default } from \"-!../node_modules/thread-loader/dist/cjs.js!../node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[1]!../node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./App.vue?vue&type=script&setup=true&lang=js\"; export * from \"-!../node_modules/thread-loader/dist/cjs.js!../node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[1]!../node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./App.vue?vue&type=script&setup=true&lang=js\"","export { default } from \"-!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[1]!../../node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./DocumentedShell.vue?vue&type=script&setup=true&lang=js\"; export * from \"-!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[1]!../../node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./DocumentedShell.vue?vue&type=script&setup=true&lang=js\"","export { default } from \"-!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[1]!../../node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./LoginView.vue?vue&type=script&setup=true&lang=js\"; export * from \"-!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[1]!../../node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./LoginView.vue?vue&type=script&setup=true&lang=js\"","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","var deferred = [];\n__webpack_require__.O = function(result, chunkIds, fn, priority) {\n\tif(chunkIds) {\n\t\tpriority = priority || 0;\n\t\tfor(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1];\n\t\tdeferred[i] = [chunkIds, fn, priority];\n\t\treturn;\n\t}\n\tvar notFulfilled = Infinity;\n\tfor (var i = 0; i < deferred.length; i++) {\n\t\tvar chunkIds = deferred[i][0];\n\t\tvar fn = deferred[i][1];\n\t\tvar priority = deferred[i][2];\n\t\tvar fulfilled = true;\n\t\tfor (var j = 0; j < chunkIds.length; j++) {\n\t\t\tif ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every(function(key) { return __webpack_require__.O[key](chunkIds[j]); })) {\n\t\t\t\tchunkIds.splice(j--, 1);\n\t\t\t} else {\n\t\t\t\tfulfilled = false;\n\t\t\t\tif(priority < notFulfilled) notFulfilled = priority;\n\t\t\t}\n\t\t}\n\t\tif(fulfilled) {\n\t\t\tdeferred.splice(i--, 1)\n\t\t\tvar r = fn();\n\t\t\tif (r !== undefined) result = r;\n\t\t}\n\t}\n\treturn result;\n};","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = function(module) {\n\tvar getter = module && module.__esModule ?\n\t\tfunction() { return module['default']; } :\n\t\tfunction() { return module; };\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter/value functions for harmony exports\n__webpack_require__.d = function(exports, definition) {\n\tif(Array.isArray(definition)) {\n\t\tvar i = 0;\n\t\twhile(i < definition.length) {\n\t\t\tvar key = definition[i++];\n\t\t\tvar binding = definition[i++];\n\t\t\tif(!__webpack_require__.o(exports, key)) {\n\t\t\t\tif(binding === 0) {\n\t\t\t\t\tObject.defineProperty(exports, key, { enumerable: true, value: definition[i++] });\n\t\t\t\t} else {\n\t\t\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: binding });\n\t\t\t\t}\n\t\t\t} else if(binding === 0) { i++; }\n\t\t}\n\t} else {\n\t\tfor(var key in definition) {\n\t\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t\t}\n\t\t}\n\t}\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }","// define __esModule on exports\n__webpack_require__.r = function(exports) {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","// set .name for anonymous default exports per ES spec\n__webpack_require__.dn = function(x) {\n\t(Object.getOwnPropertyDescriptor(x, \"name\") || {}).writable || Object.defineProperty(x, \"name\", { value: \"default\", configurable: true });\n};","// no baseURI\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t524: 0\n};\n\n// no chunk on demand loading\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n__webpack_require__.O.j = function(chunkId) { return installedChunks[chunkId] === 0; };\n\n// install a JSONP callback for chunk loading\nvar webpackJsonpCallback = function(parentChunkLoadingFunction, data) {\n\tvar chunkIds = data[0];\n\tvar moreModules = data[1];\n\tvar runtime = data[2];\n\t// add \"moreModules\" to the modules object,\n\t// then flag all \"chunkIds\" as loaded and fire callback\n\tvar moduleId, chunkId, i = 0;\n\tif(chunkIds.some(function(id) { return installedChunks[id] !== 0; })) {\n\t\tfor(moduleId in moreModules) {\n\t\t\tif(__webpack_require__.o(moreModules, moduleId)) {\n\t\t\t\t__webpack_require__.m[moduleId] = moreModules[moduleId];\n\t\t\t}\n\t\t}\n\t\tif(runtime) var result = runtime(__webpack_require__);\n\t}\n\tif(parentChunkLoadingFunction) parentChunkLoadingFunction(data);\n\tfor(;i < chunkIds.length; i++) {\n\t\tchunkId = chunkIds[i];\n\t\tif(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {\n\t\t\tinstalledChunks[chunkId][0]();\n\t\t}\n\t\tinstalledChunks[chunkId] = 0;\n\t}\n\treturn __webpack_require__.O(result);\n}\n\nvar chunkLoadingGlobal = self[\"webpackChunkjc_web_im\"] = self[\"webpackChunkjc_web_im\"] || [];\nchunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));\nchunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));","","// startup\n// Load entry module and return exports\n// This entry module depends on other loaded chunks and execution need to be delayed\nvar __webpack_exports__ = __webpack_require__.O(undefined, [504], function() { return __webpack_require__(1406); })\n__webpack_exports__ = __webpack_require__.O(__webpack_exports__);\n",""],"names":["onMounted","LoginView","DocumentedShell","useSessionStore","session","restore","computed","reactive","ref","appConfig","passwordDialogOpen","passwordSaving","passwordError","passwordNotice","passwordDraft","oldPassword","newPassword","confirmPassword","initials","String","displayName","userId","slice","toUpperCase","resetPasswordDraft","openPasswordDialog","value","closePasswordDialog","savePassword","changePassword","error","message","logout","axios","jrQRCode","onBeforeUnmount","imClient","QR_CODE_PREFIX_PC_SESSION","defaultPlatformLogo","view","loginMode","loginAccount","loginPassword","registerType","registerForm","number","code","password","invitationCode","sendingCode","info","qrStatus","qrCodeImage","qrSessionToken","qrUserName","qrUserPortrait","qrGeneration","qrExpireTimer","defaultPlatformName","appName","platformName","platformLogo","canSubmitLogin","length","canRequestEmailCode","test","canSubmitRegister","qrUserInitial","qrStatusTitle","connecting","qrStatusText","qrRefreshText","loadPlatformConfig","stopQrLogin","clearFormMessage","setLoginMode","mode","startQrLogin","submitLogin","type","loginWithPassword","switchRegisterType","requestEmailCode","requestAuthCode","submitRegister","result","register","openRegister","backToLogin","resetQrState","clearQrExpireTimer","window","clearTimeout","refreshQrLogin","force","includes","generation","pcSession","createPcSession","token","Error","getQrBase64","expireMs","Math","max","min","Number","expired","setTimeout","isAuthenticated","pollQrLogin","payload","loginWithPcSession","data","status","imToken","completePcSessionLogin","userName","portrait","delay","ms","Promise","resolve","response","get","platformConfigApi","withCredentials","config","name","trim","logo","logoUrl","version","resolveBrandLogo","applyDocumentBrand","appendBrandVersion","startsWith","location","protocol","base","adminServer","URL","toString","url","parsed","href","searchParams","set","document","title","icon","querySelector","createElement","setAttribute","head","appendChild","handleBrandLogoError","currentHost","hostname","runtimeConfig","JC_WEB_IM_CONFIG","appServerOrigin","qrAppServerOrigin","adminServerOrigin","process","env","VUE_APP_ADMIN_SERVER","appServer","VUE_APP_APP_SERVER","qrAppServer","VUE_APP_QR_APP_SERVER","VUE_APP_PLATFORM_CONFIG_API","NODE_ENV","registerConfigApi","VUE_APP_REGISTER_CONFIG_API","registerPhoneExistsApi","VUE_APP_REGISTER_PHONE_EXISTS_API","contactsApi","VUE_APP_CONTACTS_API","updateAliasApi","VUE_APP_UPDATE_ALIAS_API","addContactApi","VUE_APP_ADD_CONTACT_API","conversationActionsApi","VUE_APP_CONVERSATION_ACTIONS_API","sendMessageApi","VUE_APP_SEND_MESSAGE_API","messageActionApi","VUE_APP_MESSAGE_ACTION_API","messageDetailApi","VUE_APP_MESSAGE_DETAIL_API","messagesApi","VUE_APP_MESSAGES_API","profileApi","VUE_APP_PROFILE_API","changePasswordApi","VUE_APP_CHANGE_PASSWORD_API","logoutApi","VUE_APP_LOGOUT_API","imServerHost","VUE_APP_IM_SERVER_HOST","imRoutePort","VUE_APP_IM_ROUTE_PORT","useWss","VUE_APP_USE_WSS","wsPath","VUE_APP_WS_PATH","wsUrl","VUE_APP_WS_URL","sdkMode","VUE_APP_IM_MODE","syncIntervalMs","VUE_APP_SYNC_INTERVAL_MS","platform","clientIdStorageKey","authTokenStoragePrefix","createApp","createPinia","App","use","mount","AES_KEY","ENCRYPTION_EXCLUDE_PATHS","AUTH_STORAGE_KEY","AUTH_ERROR_CODE","REFRESH_ERROR_CODE","importedCryptoKeyPromise","refreshPromise","storageScope","readAuthStore","JSON","parse","localStorage","getItem","writeAuthStore","store","setItem","stringify","normalizeAuthBundle","bundle","accessToken","refreshToken","tokenType","accessTokenExpireIn","accessTokenExpireAt","refreshTokenExpireIn","refreshTokenExpireAt","getStoredAppAuth","persistAppAuth","normalized","clearStoredAppAuth","hasFormData","FormData","absoluteUrl","origin","shouldEncrypt","pathname","some","path","shouldAttachAuth","skipAuth","textEncoder","TextEncoder","textDecoder","TextDecoder","bytesToBase64","bytes","binary","forEach","byte","fromCharCode","btoa","base64ToBytes","base64","atob","Uint8Array","index","charCodeAt","getCryptoKey","crypto","subtle","importKey","encode","encryptPayload","plainText","iv","getRandomValues","key","encrypted","encrypt","decryptPayload","cipherText","ivBase64","encryptedBase64","split","decrypted","decrypt","decode","normalizeBusinessResponse","Array","isArray","decodeResponsePayload","headers","toLowerCase","client","create","interceptors","request","nextConfig","auth","Authorization","method","plainBody","floor","Date","now","refreshAccessToken","post","then","finally","_retriedAfterRefresh","appServerRequest","appServerGet","appServerPost","HttpImClient","inferAccountType","account","unwrapData","unwrapMessage","fallback","msg","DocumentedHttpImClient","unsupported","feature","clientId","getClientId","email","loginWithCode","flag","device_name","encodeURIComponent","postError","persistAppToken","resetPasswordWithCode","mobile","resetCode","fetchRemoteConversations","fetchRemoteMessages","getServerContactList","getProfile","sendTextMessage","sendAttachmentMessage","addContact","updateContactAlias","createGroup","joinGroup","broadcastMessage","getGroupDetail","getGroupMembers","updateGroupAlias","leaveGroup","dismissGroup","transferGroup","setGroupAdmins","updateGroupProfile","kickGroupMembers","updateGroupMuteSetting","updateProfile","recallMessage","editMessage","getMessageDetail","LOCAL_MESSAGE_STORAGE_KEY","LOCAL_HIDDEN_MESSAGE_STORAGE_KEY","LOCAL_RECALLED_MESSAGE_STORAGE_KEY","scopedStorageKey","baseKey","scope","randomId","randomUUID","replaceAll","random","readLocalMessages","readMessageSet","storageKey","writeMessageSet","messagesByConversation","messageKeys","id","messageUid","filter","Boolean","map","hasStoredMessage","conversationId","stored","ids","Set","has","storeMessageKeys","messages","add","from","withRecalledState","deliveryStatus","recalled","recallStatus","applyLocalMessageState","isRecalled","writeLocalMessages","saveLocalMessage","findIndex","item","splice","push","mergeSyncedMessages","localMessages","remoteMessages","visibleLocalMessages","localByKey","Map","visibleRemoteMessages","remoteMessage","text","localMessage","find","remoteKeys","hasRemoteMatch","direction","abs","timestamp","pendingLocalMessages","sort","a","b","displayTimestamp","getTitle","getUniqueEquipment","allowedDevices","canUseLocalLoginFallback","constructor","listeners","connected","currentUser","restoreClientId","getSdkVersion","ensureSdk","deviceId","uniqueEquipment","localOnly","registerWithPhone","connect","emit","statusText","refreshConversationList","disconnect","getConversationList","conversations","remoteConversations","catch","limit","conversation","updatedAt","avatarText","target","raw","line","getMessages","options","replace","conversationType","markRead","hideMessages","items","markMessagesRecalled","action","nextText","editedMessage","editedAt","activityTimestamp","detail","initialMessage","senderRole","role","sender","sentMessage","serverTimestamp","failedMessage","file","attachment","mime","size","createObjectURL","formData","append","getContactList","getAppAuthHeaders","contacts","mapContact","uid","alias","address","friendAlias","localeCompare","keyword","reason","contact","normalizeConversation","subtitle","unread","memberIds","announcement","welcomeText","channel","groupKey","targetIds","groupId","group","members","targetUserId","adminIds","setting","muteSetting","targetId","profile","portraitFile","resetPasswordApi","refreshContactList","createSingleConversation","userInfo","friend","rawFriend","rawUser","on","eventName","handler","off","delete","appToken","authtoken","authToken","getUrlHost","host","LongConnectionImClient","UnsupportedWildfireImClient","WildfireImClient","HEARTBEAT_INTERVAL_MS","RECONNECT_MIN_MS","RECONNECT_MAX_MS","buildWsUrl","port","normalizeConversationId","to","receiver","conversationTarget","normalizeIncomingMessage","currentUserId","fromUser","messageId","content","searchableContent","socket","heartbeatTimer","reconnectTimer","reconnectAttempts","manualClose","shouldUseMessagePolling","WebSocket","openSocket","clearReconnectTimer","clearHeartbeatTimer","close","readyState","OPEN","sendAuthFrame","CONNECTING","binaryType","scheduleReconnect","onopen","startHeartbeat","onmessage","event","handleFrame","onerror","onclose","sendFrame","frame","send","rawData","ArrayBuffer","cmd","setInterval","clearInterval","defineStore","STORAGE_KEY","normalizeStoredPortrait","nextProfilePortrait","profilePortrait","currentPortrait","state","connectionStatusText","sdkVersion","eventsBound","actions","removeItem","refreshProfile","applyLoginResult","persistSession","applyProfile","saveProfile","bindImEvents","$reset"],"sourceRoot":""} \ No newline at end of file diff --git a/dist/js/chunk-vendors.cd0faf68.js b/dist/js/chunk-vendors.cd0faf68.js new file mode 100644 index 0000000..7888f04 --- /dev/null +++ b/dist/js/chunk-vendors.cd0faf68.js @@ -0,0 +1,24967 @@ +(self["webpackChunkjc_web_im"] = self["webpackChunkjc_web_im"] || []).push([[504],{ + +/***/ 144: +/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +"use strict"; +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ C4: function() { return /* binding */ pauseTracking; }, +/* harmony export */ EW: function() { return /* binding */ computed; }, +/* harmony export */ Gc: function() { return /* binding */ shallowReactive; }, +/* harmony export */ IG: function() { return /* binding */ markRaw; }, +/* harmony export */ KR: function() { return /* binding */ ref; }, +/* harmony export */ Kh: function() { return /* binding */ reactive; }, +/* harmony export */ Pr: function() { return /* binding */ proxyRefs; }, +/* harmony export */ QW: function() { return /* binding */ toRefs; }, +/* harmony export */ R1: function() { return /* binding */ unref; }, +/* harmony export */ X2: function() { return /* binding */ ReactiveEffect; }, +/* harmony export */ bl: function() { return /* binding */ resetTracking; }, +/* harmony export */ g8: function() { return /* binding */ isReactive; }, +/* harmony export */ hV: function() { return /* binding */ traverse; }, +/* harmony export */ hZ: function() { return /* binding */ trigger; }, +/* harmony export */ i9: function() { return /* binding */ isRef; }, +/* harmony export */ jr: function() { return /* binding */ onScopeDispose; }, +/* harmony export */ ju: function() { return /* binding */ isProxy; }, +/* harmony export */ o5: function() { return /* binding */ getCurrentScope; }, +/* harmony export */ u4: function() { return /* binding */ track; }, +/* harmony export */ uY: function() { return /* binding */ effectScope; }, +/* harmony export */ ux: function() { return /* binding */ toRaw; }, +/* harmony export */ wB: function() { return /* binding */ watch; }, +/* harmony export */ yC: function() { return /* binding */ EffectScope; } +/* harmony export */ }); +/* unused harmony exports ARRAY_ITERATE_KEY, EffectFlags, ITERATE_KEY, MAP_KEY_ITERATE_KEY, ReactiveFlags, TrackOpTypes, TriggerOpTypes, WatchErrorCodes, customRef, effect, enableTracking, getCurrentWatcher, isReadonly, isShallow, onEffectCleanup, onWatcherCleanup, reactiveReadArray, readonly, shallowReadArray, shallowReadonly, shallowRef, stop, toReactive, toReadonly, toRef, toValue, triggerRef */ +/* unused harmony import specifier */ var extend; +/* unused harmony import specifier */ var toRawType; +/* unused harmony import specifier */ var isFunction; +/* unused harmony import specifier */ var isObject; +/* harmony import */ var core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4114); +/* harmony import */ var core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var core_js_modules_es_iterator_constructor_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(8111); +/* harmony import */ var core_js_modules_es_iterator_constructor_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_constructor_js__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var core_js_modules_es_iterator_filter_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(2489); +/* harmony import */ var core_js_modules_es_iterator_filter_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_filter_js__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var core_js_modules_es_iterator_for_each_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(7588); +/* harmony import */ var core_js_modules_es_iterator_for_each_js__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_for_each_js__WEBPACK_IMPORTED_MODULE_3__); +/* harmony import */ var core_js_modules_es_iterator_map_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(1701); +/* harmony import */ var core_js_modules_es_iterator_map_js__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_map_js__WEBPACK_IMPORTED_MODULE_4__); +/* harmony import */ var core_js_modules_es_iterator_some_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(3579); +/* harmony import */ var core_js_modules_es_iterator_some_js__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_some_js__WEBPACK_IMPORTED_MODULE_5__); +/* harmony import */ var core_js_modules_es_map_get_or_insert_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(5367); +/* harmony import */ var core_js_modules_es_map_get_or_insert_js__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_map_get_or_insert_js__WEBPACK_IMPORTED_MODULE_6__); +/* harmony import */ var core_js_modules_es_map_get_or_insert_computed_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(2731); +/* harmony import */ var core_js_modules_es_map_get_or_insert_computed_js__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_map_get_or_insert_computed_js__WEBPACK_IMPORTED_MODULE_7__); +/* harmony import */ var core_js_modules_es_set_difference_v2_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(7642); +/* harmony import */ var core_js_modules_es_set_difference_v2_js__WEBPACK_IMPORTED_MODULE_8___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_set_difference_v2_js__WEBPACK_IMPORTED_MODULE_8__); +/* harmony import */ var core_js_modules_es_set_intersection_v2_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(8004); +/* harmony import */ var core_js_modules_es_set_intersection_v2_js__WEBPACK_IMPORTED_MODULE_9___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_set_intersection_v2_js__WEBPACK_IMPORTED_MODULE_9__); +/* harmony import */ var core_js_modules_es_set_is_disjoint_from_v2_js__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(3853); +/* harmony import */ var core_js_modules_es_set_is_disjoint_from_v2_js__WEBPACK_IMPORTED_MODULE_10___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_set_is_disjoint_from_v2_js__WEBPACK_IMPORTED_MODULE_10__); +/* harmony import */ var core_js_modules_es_set_is_subset_of_v2_js__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(5876); +/* harmony import */ var core_js_modules_es_set_is_subset_of_v2_js__WEBPACK_IMPORTED_MODULE_11___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_set_is_subset_of_v2_js__WEBPACK_IMPORTED_MODULE_11__); +/* harmony import */ var core_js_modules_es_set_is_superset_of_v2_js__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(2475); +/* harmony import */ var core_js_modules_es_set_is_superset_of_v2_js__WEBPACK_IMPORTED_MODULE_12___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_set_is_superset_of_v2_js__WEBPACK_IMPORTED_MODULE_12__); +/* harmony import */ var core_js_modules_es_set_symmetric_difference_v2_js__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(5024); +/* harmony import */ var core_js_modules_es_set_symmetric_difference_v2_js__WEBPACK_IMPORTED_MODULE_13___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_set_symmetric_difference_v2_js__WEBPACK_IMPORTED_MODULE_13__); +/* harmony import */ var core_js_modules_es_set_union_v2_js__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(1698); +/* harmony import */ var core_js_modules_es_set_union_v2_js__WEBPACK_IMPORTED_MODULE_14___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_set_union_v2_js__WEBPACK_IMPORTED_MODULE_14__); +/* harmony import */ var core_js_modules_es_weak_map_get_or_insert_js__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(8454); +/* harmony import */ var core_js_modules_es_weak_map_get_or_insert_js__WEBPACK_IMPORTED_MODULE_15___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_weak_map_get_or_insert_js__WEBPACK_IMPORTED_MODULE_15__); +/* harmony import */ var core_js_modules_es_weak_map_get_or_insert_computed_js__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(9452); +/* harmony import */ var core_js_modules_es_weak_map_get_or_insert_computed_js__WEBPACK_IMPORTED_MODULE_16___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_weak_map_get_or_insert_computed_js__WEBPACK_IMPORTED_MODULE_16__); +/* harmony import */ var _vue_shared__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(6107); + + + + + + + + + + + + + + + + + +/** +* @vue/reactivity v3.5.13 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/ + +function warn(msg, ...args) { + console.warn(`[Vue warn] ${msg}`, ...args); +} +let activeEffectScope; +class EffectScope { + constructor(detached = false) { + this.detached = detached; + /** + * @internal + */ + this._active = true; + /** + * @internal + */ + this.effects = []; + /** + * @internal + */ + this.cleanups = []; + this._isPaused = false; + this.parent = activeEffectScope; + if (!detached && activeEffectScope) { + this.index = (activeEffectScope.scopes || (activeEffectScope.scopes = [])).push(this) - 1; + } + } + get active() { + return this._active; + } + pause() { + if (this._active) { + this._isPaused = true; + let i, l; + if (this.scopes) { + for (i = 0, l = this.scopes.length; i < l; i++) { + this.scopes[i].pause(); + } + } + for (i = 0, l = this.effects.length; i < l; i++) { + this.effects[i].pause(); + } + } + } + /** + * Resumes the effect scope, including all child scopes and effects. + */ + resume() { + if (this._active) { + if (this._isPaused) { + this._isPaused = false; + let i, l; + if (this.scopes) { + for (i = 0, l = this.scopes.length; i < l; i++) { + this.scopes[i].resume(); + } + } + for (i = 0, l = this.effects.length; i < l; i++) { + this.effects[i].resume(); + } + } + } + } + run(fn) { + if (this._active) { + const currentEffectScope = activeEffectScope; + try { + activeEffectScope = this; + return fn(); + } finally { + activeEffectScope = currentEffectScope; + } + } else if (false) // removed by dead control flow +{} + } + /** + * This should only be called on non-detached scopes + * @internal + */ + on() { + activeEffectScope = this; + } + /** + * This should only be called on non-detached scopes + * @internal + */ + off() { + activeEffectScope = this.parent; + } + stop(fromParent) { + if (this._active) { + this._active = false; + let i, l; + for (i = 0, l = this.effects.length; i < l; i++) { + this.effects[i].stop(); + } + this.effects.length = 0; + for (i = 0, l = this.cleanups.length; i < l; i++) { + this.cleanups[i](); + } + this.cleanups.length = 0; + if (this.scopes) { + for (i = 0, l = this.scopes.length; i < l; i++) { + this.scopes[i].stop(true); + } + this.scopes.length = 0; + } + if (!this.detached && this.parent && !fromParent) { + const last = this.parent.scopes.pop(); + if (last && last !== this) { + this.parent.scopes[this.index] = last; + last.index = this.index; + } + } + this.parent = void 0; + } + } +} +function effectScope(detached) { + return new EffectScope(detached); +} +function getCurrentScope() { + return activeEffectScope; +} +function onScopeDispose(fn, failSilently = false) { + if (activeEffectScope) { + activeEffectScope.cleanups.push(fn); + } else if (false) // removed by dead control flow +{} +} +let activeSub; +const EffectFlags = (/* unused pure expression or super */ null && ({ + "ACTIVE": 1, + "1": "ACTIVE", + "RUNNING": 2, + "2": "RUNNING", + "TRACKING": 4, + "4": "TRACKING", + "NOTIFIED": 8, + "8": "NOTIFIED", + "DIRTY": 16, + "16": "DIRTY", + "ALLOW_RECURSE": 32, + "32": "ALLOW_RECURSE", + "PAUSED": 64, + "64": "PAUSED" +})); +const pausedQueueEffects = /* @__PURE__ */new WeakSet(); +class ReactiveEffect { + constructor(fn) { + this.fn = fn; + /** + * @internal + */ + this.deps = void 0; + /** + * @internal + */ + this.depsTail = void 0; + /** + * @internal + */ + this.flags = 1 | 4; + /** + * @internal + */ + this.next = void 0; + /** + * @internal + */ + this.cleanup = void 0; + this.scheduler = void 0; + if (activeEffectScope && activeEffectScope.active) { + activeEffectScope.effects.push(this); + } + } + pause() { + this.flags |= 64; + } + resume() { + if (this.flags & 64) { + this.flags &= ~64; + if (pausedQueueEffects.has(this)) { + pausedQueueEffects.delete(this); + this.trigger(); + } + } + } + /** + * @internal + */ + notify() { + if (this.flags & 2 && !(this.flags & 32)) { + return; + } + if (!(this.flags & 8)) { + batch(this); + } + } + run() { + if (!(this.flags & 1)) { + return this.fn(); + } + this.flags |= 2; + cleanupEffect(this); + prepareDeps(this); + const prevEffect = activeSub; + const prevShouldTrack = shouldTrack; + activeSub = this; + shouldTrack = true; + try { + return this.fn(); + } finally { + if (false) // removed by dead control flow +{} + cleanupDeps(this); + activeSub = prevEffect; + shouldTrack = prevShouldTrack; + this.flags &= ~2; + } + } + stop() { + if (this.flags & 1) { + for (let link = this.deps; link; link = link.nextDep) { + removeSub(link); + } + this.deps = this.depsTail = void 0; + cleanupEffect(this); + this.onStop && this.onStop(); + this.flags &= ~1; + } + } + trigger() { + if (this.flags & 64) { + pausedQueueEffects.add(this); + } else if (this.scheduler) { + this.scheduler(); + } else { + this.runIfDirty(); + } + } + /** + * @internal + */ + runIfDirty() { + if (isDirty(this)) { + this.run(); + } + } + get dirty() { + return isDirty(this); + } +} +let batchDepth = 0; +let batchedSub; +let batchedComputed; +function batch(sub, isComputed = false) { + sub.flags |= 8; + if (isComputed) { + sub.next = batchedComputed; + batchedComputed = sub; + return; + } + sub.next = batchedSub; + batchedSub = sub; +} +function startBatch() { + batchDepth++; +} +function endBatch() { + if (--batchDepth > 0) { + return; + } + if (batchedComputed) { + let e = batchedComputed; + batchedComputed = void 0; + while (e) { + const next = e.next; + e.next = void 0; + e.flags &= ~8; + e = next; + } + } + let error; + while (batchedSub) { + let e = batchedSub; + batchedSub = void 0; + while (e) { + const next = e.next; + e.next = void 0; + e.flags &= ~8; + if (e.flags & 1) { + try { + ; + e.trigger(); + } catch (err) { + if (!error) error = err; + } + } + e = next; + } + } + if (error) throw error; +} +function prepareDeps(sub) { + for (let link = sub.deps; link; link = link.nextDep) { + link.version = -1; + link.prevActiveLink = link.dep.activeLink; + link.dep.activeLink = link; + } +} +function cleanupDeps(sub) { + let head; + let tail = sub.depsTail; + let link = tail; + while (link) { + const prev = link.prevDep; + if (link.version === -1) { + if (link === tail) tail = prev; + removeSub(link); + removeDep(link); + } else { + head = link; + } + link.dep.activeLink = link.prevActiveLink; + link.prevActiveLink = void 0; + link = prev; + } + sub.deps = head; + sub.depsTail = tail; +} +function isDirty(sub) { + for (let link = sub.deps; link; link = link.nextDep) { + if (link.dep.version !== link.version || link.dep.computed && (refreshComputed(link.dep.computed) || link.dep.version !== link.version)) { + return true; + } + } + if (sub._dirty) { + return true; + } + return false; +} +function refreshComputed(computed) { + if (computed.flags & 4 && !(computed.flags & 16)) { + return; + } + computed.flags &= ~16; + if (computed.globalVersion === globalVersion) { + return; + } + computed.globalVersion = globalVersion; + const dep = computed.dep; + computed.flags |= 2; + if (dep.version > 0 && !computed.isSSR && computed.deps && !isDirty(computed)) { + computed.flags &= ~2; + return; + } + const prevSub = activeSub; + const prevShouldTrack = shouldTrack; + activeSub = computed; + shouldTrack = true; + try { + prepareDeps(computed); + const value = computed.fn(computed._value); + if (dep.version === 0 || (0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .hasChanged */ .$H)(value, computed._value)) { + computed._value = value; + dep.version++; + } + } catch (err) { + dep.version++; + throw err; + } finally { + activeSub = prevSub; + shouldTrack = prevShouldTrack; + cleanupDeps(computed); + computed.flags &= ~2; + } +} +function removeSub(link, soft = false) { + const { + dep, + prevSub, + nextSub + } = link; + if (prevSub) { + prevSub.nextSub = nextSub; + link.prevSub = void 0; + } + if (nextSub) { + nextSub.prevSub = prevSub; + link.nextSub = void 0; + } + if (false) // removed by dead control flow +{} + if (dep.subs === link) { + dep.subs = prevSub; + if (!prevSub && dep.computed) { + dep.computed.flags &= ~4; + for (let l = dep.computed.deps; l; l = l.nextDep) { + removeSub(l, true); + } + } + } + if (!soft && ! --dep.sc && dep.map) { + dep.map.delete(dep.key); + } +} +function removeDep(link) { + const { + prevDep, + nextDep + } = link; + if (prevDep) { + prevDep.nextDep = nextDep; + link.prevDep = void 0; + } + if (nextDep) { + nextDep.prevDep = prevDep; + link.nextDep = void 0; + } +} +function effect(fn, options) { + if (fn.effect instanceof ReactiveEffect) { + fn = fn.effect.fn; + } + const e = new ReactiveEffect(fn); + if (options) { + extend(e, options); + } + try { + e.run(); + } catch (err) { + e.stop(); + throw err; + } + const runner = e.run.bind(e); + runner.effect = e; + return runner; +} +function stop(runner) { + runner.effect.stop(); +} +let shouldTrack = true; +const trackStack = []; +function pauseTracking() { + trackStack.push(shouldTrack); + shouldTrack = false; +} +function enableTracking() { + trackStack.push(shouldTrack); + shouldTrack = true; +} +function resetTracking() { + const last = trackStack.pop(); + shouldTrack = last === void 0 ? true : last; +} +function onEffectCleanup(fn, failSilently = false) { + if (activeSub instanceof ReactiveEffect) { + activeSub.cleanup = fn; + } else if (false) // removed by dead control flow +{} +} +function cleanupEffect(e) { + const { + cleanup + } = e; + e.cleanup = void 0; + if (cleanup) { + const prevSub = activeSub; + activeSub = void 0; + try { + cleanup(); + } finally { + activeSub = prevSub; + } + } +} +let globalVersion = 0; +class Link { + constructor(sub, dep) { + this.sub = sub; + this.dep = dep; + this.version = dep.version; + this.nextDep = this.prevDep = this.nextSub = this.prevSub = this.prevActiveLink = void 0; + } +} +class Dep { + constructor(computed) { + this.computed = computed; + this.version = 0; + /** + * Link between this dep and the current active effect + */ + this.activeLink = void 0; + /** + * Doubly linked list representing the subscribing effects (tail) + */ + this.subs = void 0; + /** + * For object property deps cleanup + */ + this.map = void 0; + this.key = void 0; + /** + * Subscriber counter + */ + this.sc = 0; + if (false) // removed by dead control flow +{} + } + track(debugInfo) { + if (!activeSub || !shouldTrack || activeSub === this.computed) { + return; + } + let link = this.activeLink; + if (link === void 0 || link.sub !== activeSub) { + link = this.activeLink = new Link(activeSub, this); + if (!activeSub.deps) { + activeSub.deps = activeSub.depsTail = link; + } else { + link.prevDep = activeSub.depsTail; + activeSub.depsTail.nextDep = link; + activeSub.depsTail = link; + } + addSub(link); + } else if (link.version === -1) { + link.version = this.version; + if (link.nextDep) { + const next = link.nextDep; + next.prevDep = link.prevDep; + if (link.prevDep) { + link.prevDep.nextDep = next; + } + link.prevDep = activeSub.depsTail; + link.nextDep = void 0; + activeSub.depsTail.nextDep = link; + activeSub.depsTail = link; + if (activeSub.deps === link) { + activeSub.deps = next; + } + } + } + if (false) // removed by dead control flow +{} + return link; + } + trigger(debugInfo) { + this.version++; + globalVersion++; + this.notify(debugInfo); + } + notify(debugInfo) { + startBatch(); + try { + if (false) // removed by dead control flow +{} + for (let link = this.subs; link; link = link.prevSub) { + if (link.sub.notify()) { + ; + link.sub.dep.notify(); + } + } + } finally { + endBatch(); + } + } +} +function addSub(link) { + link.dep.sc++; + if (link.sub.flags & 4) { + const computed = link.dep.computed; + if (computed && !link.dep.subs) { + computed.flags |= 4 | 16; + for (let l = computed.deps; l; l = l.nextDep) { + addSub(l); + } + } + const currentTail = link.dep.subs; + if (currentTail !== link) { + link.prevSub = currentTail; + if (currentTail) currentTail.nextSub = link; + } + if (false) // removed by dead control flow +{} + link.dep.subs = link; + } +} +const targetMap = /* @__PURE__ */new WeakMap(); +const ITERATE_KEY = Symbol( false ? 0 : ""); +const MAP_KEY_ITERATE_KEY = Symbol( false ? 0 : ""); +const ARRAY_ITERATE_KEY = Symbol( false ? 0 : ""); +function track(target, type, key) { + if (shouldTrack && activeSub) { + let depsMap = targetMap.get(target); + if (!depsMap) { + targetMap.set(target, depsMap = /* @__PURE__ */new Map()); + } + let dep = depsMap.get(key); + if (!dep) { + depsMap.set(key, dep = new Dep()); + dep.map = depsMap; + dep.key = key; + } + if (false) // removed by dead control flow +{} else { + dep.track(); + } + } +} +function trigger(target, type, key, newValue, oldValue, oldTarget) { + const depsMap = targetMap.get(target); + if (!depsMap) { + globalVersion++; + return; + } + const run = dep => { + if (dep) { + if (false) // removed by dead control flow +{} else { + dep.trigger(); + } + } + }; + startBatch(); + if (type === "clear") { + depsMap.forEach(run); + } else { + const targetIsArray = (0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .isArray */ .cy)(target); + const isArrayIndex = targetIsArray && (0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .isIntegerKey */ .yI)(key); + if (targetIsArray && key === "length") { + const newLength = Number(newValue); + depsMap.forEach((dep, key2) => { + if (key2 === "length" || key2 === ARRAY_ITERATE_KEY || !(0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .isSymbol */ .Bm)(key2) && key2 >= newLength) { + run(dep); + } + }); + } else { + if (key !== void 0 || depsMap.has(void 0)) { + run(depsMap.get(key)); + } + if (isArrayIndex) { + run(depsMap.get(ARRAY_ITERATE_KEY)); + } + switch (type) { + case "add": + if (!targetIsArray) { + run(depsMap.get(ITERATE_KEY)); + if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .isMap */ .CE)(target)) { + run(depsMap.get(MAP_KEY_ITERATE_KEY)); + } + } else if (isArrayIndex) { + run(depsMap.get("length")); + } + break; + case "delete": + if (!targetIsArray) { + run(depsMap.get(ITERATE_KEY)); + if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .isMap */ .CE)(target)) { + run(depsMap.get(MAP_KEY_ITERATE_KEY)); + } + } + break; + case "set": + if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .isMap */ .CE)(target)) { + run(depsMap.get(ITERATE_KEY)); + } + break; + } + } + } + endBatch(); +} +function getDepFromReactive(object, key) { + const depMap = targetMap.get(object); + return depMap && depMap.get(key); +} +function reactiveReadArray(array) { + const raw = toRaw(array); + if (raw === array) return raw; + track(raw, "iterate", ARRAY_ITERATE_KEY); + return isShallow(array) ? raw : raw.map(toReactive); +} +function shallowReadArray(arr) { + track(arr = toRaw(arr), "iterate", ARRAY_ITERATE_KEY); + return arr; +} +const arrayInstrumentations = { + __proto__: null, + [Symbol.iterator]() { + return iterator(this, Symbol.iterator, toReactive); + }, + concat(...args) { + return reactiveReadArray(this).concat(...args.map(x => (0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .isArray */ .cy)(x) ? reactiveReadArray(x) : x)); + }, + entries() { + return iterator(this, "entries", value => { + value[1] = toReactive(value[1]); + return value; + }); + }, + every(fn, thisArg) { + return apply(this, "every", fn, thisArg, void 0, arguments); + }, + filter(fn, thisArg) { + return apply(this, "filter", fn, thisArg, v => v.map(toReactive), arguments); + }, + find(fn, thisArg) { + return apply(this, "find", fn, thisArg, toReactive, arguments); + }, + findIndex(fn, thisArg) { + return apply(this, "findIndex", fn, thisArg, void 0, arguments); + }, + findLast(fn, thisArg) { + return apply(this, "findLast", fn, thisArg, toReactive, arguments); + }, + findLastIndex(fn, thisArg) { + return apply(this, "findLastIndex", fn, thisArg, void 0, arguments); + }, + // flat, flatMap could benefit from ARRAY_ITERATE but are not straight-forward to implement + forEach(fn, thisArg) { + return apply(this, "forEach", fn, thisArg, void 0, arguments); + }, + includes(...args) { + return searchProxy(this, "includes", args); + }, + indexOf(...args) { + return searchProxy(this, "indexOf", args); + }, + join(separator) { + return reactiveReadArray(this).join(separator); + }, + // keys() iterator only reads `length`, no optimisation required + lastIndexOf(...args) { + return searchProxy(this, "lastIndexOf", args); + }, + map(fn, thisArg) { + return apply(this, "map", fn, thisArg, void 0, arguments); + }, + pop() { + return noTracking(this, "pop"); + }, + push(...args) { + return noTracking(this, "push", args); + }, + reduce(fn, ...args) { + return reduce(this, "reduce", fn, args); + }, + reduceRight(fn, ...args) { + return reduce(this, "reduceRight", fn, args); + }, + shift() { + return noTracking(this, "shift"); + }, + // slice could use ARRAY_ITERATE but also seems to beg for range tracking + some(fn, thisArg) { + return apply(this, "some", fn, thisArg, void 0, arguments); + }, + splice(...args) { + return noTracking(this, "splice", args); + }, + toReversed() { + return reactiveReadArray(this).toReversed(); + }, + toSorted(comparer) { + return reactiveReadArray(this).toSorted(comparer); + }, + toSpliced(...args) { + return reactiveReadArray(this).toSpliced(...args); + }, + unshift(...args) { + return noTracking(this, "unshift", args); + }, + values() { + return iterator(this, "values", toReactive); + } +}; +function iterator(self, method, wrapValue) { + const arr = shallowReadArray(self); + const iter = arr[method](); + if (arr !== self && !isShallow(self)) { + iter._next = iter.next; + iter.next = () => { + const result = iter._next(); + if (result.value) { + result.value = wrapValue(result.value); + } + return result; + }; + } + return iter; +} +const arrayProto = Array.prototype; +function apply(self, method, fn, thisArg, wrappedRetFn, args) { + const arr = shallowReadArray(self); + const needsWrap = arr !== self && !isShallow(self); + const methodFn = arr[method]; + if (methodFn !== arrayProto[method]) { + const result2 = methodFn.apply(self, args); + return needsWrap ? toReactive(result2) : result2; + } + let wrappedFn = fn; + if (arr !== self) { + if (needsWrap) { + wrappedFn = function (item, index) { + return fn.call(this, toReactive(item), index, self); + }; + } else if (fn.length > 2) { + wrappedFn = function (item, index) { + return fn.call(this, item, index, self); + }; + } + } + const result = methodFn.call(arr, wrappedFn, thisArg); + return needsWrap && wrappedRetFn ? wrappedRetFn(result) : result; +} +function reduce(self, method, fn, args) { + const arr = shallowReadArray(self); + let wrappedFn = fn; + if (arr !== self) { + if (!isShallow(self)) { + wrappedFn = function (acc, item, index) { + return fn.call(this, acc, toReactive(item), index, self); + }; + } else if (fn.length > 3) { + wrappedFn = function (acc, item, index) { + return fn.call(this, acc, item, index, self); + }; + } + } + return arr[method](wrappedFn, ...args); +} +function searchProxy(self, method, args) { + const arr = toRaw(self); + track(arr, "iterate", ARRAY_ITERATE_KEY); + const res = arr[method](...args); + if ((res === -1 || res === false) && isProxy(args[0])) { + args[0] = toRaw(args[0]); + return arr[method](...args); + } + return res; +} +function noTracking(self, method, args = []) { + pauseTracking(); + startBatch(); + const res = toRaw(self)[method].apply(self, args); + endBatch(); + resetTracking(); + return res; +} +const isNonTrackableKeys = /* @__PURE__ */(0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .makeMap */ .pD)(`__proto__,__v_isRef,__isVue`); +const builtInSymbols = new Set(/* @__PURE__ */Object.getOwnPropertyNames(Symbol).filter(key => key !== "arguments" && key !== "caller").map(key => Symbol[key]).filter(_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .isSymbol */ .Bm)); +function hasOwnProperty(key) { + if (!(0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .isSymbol */ .Bm)(key)) key = String(key); + const obj = toRaw(this); + track(obj, "has", key); + return obj.hasOwnProperty(key); +} +class BaseReactiveHandler { + constructor(_isReadonly = false, _isShallow = false) { + this._isReadonly = _isReadonly; + this._isShallow = _isShallow; + } + get(target, key, receiver) { + if (key === "__v_skip") return target["__v_skip"]; + const isReadonly2 = this._isReadonly, + isShallow2 = this._isShallow; + if (key === "__v_isReactive") { + return !isReadonly2; + } else if (key === "__v_isReadonly") { + return isReadonly2; + } else if (key === "__v_isShallow") { + return isShallow2; + } else if (key === "__v_raw") { + if (receiver === (isReadonly2 ? isShallow2 ? shallowReadonlyMap : readonlyMap : isShallow2 ? shallowReactiveMap : reactiveMap).get(target) || + // receiver is not the reactive proxy, but has the same prototype + // this means the receiver is a user proxy of the reactive proxy + Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)) { + return target; + } + return; + } + const targetIsArray = (0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .isArray */ .cy)(target); + if (!isReadonly2) { + let fn; + if (targetIsArray && (fn = arrayInstrumentations[key])) { + return fn; + } + if (key === "hasOwnProperty") { + return hasOwnProperty; + } + } + const res = Reflect.get(target, key, + // if this is a proxy wrapping a ref, return methods using the raw ref + // as receiver so that we don't have to call `toRaw` on the ref in all + // its class methods + isRef(target) ? target : receiver); + if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .isSymbol */ .Bm)(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) { + return res; + } + if (!isReadonly2) { + track(target, "get", key); + } + if (isShallow2) { + return res; + } + if (isRef(res)) { + return targetIsArray && (0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .isIntegerKey */ .yI)(key) ? res : res.value; + } + if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .isObject */ .Gv)(res)) { + return isReadonly2 ? readonly(res) : reactive(res); + } + return res; + } +} +class MutableReactiveHandler extends BaseReactiveHandler { + constructor(isShallow2 = false) { + super(false, isShallow2); + } + set(target, key, value, receiver) { + let oldValue = target[key]; + if (!this._isShallow) { + const isOldValueReadonly = isReadonly(oldValue); + if (!isShallow(value) && !isReadonly(value)) { + oldValue = toRaw(oldValue); + value = toRaw(value); + } + if (!(0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .isArray */ .cy)(target) && isRef(oldValue) && !isRef(value)) { + if (isOldValueReadonly) { + return false; + } else { + oldValue.value = value; + return true; + } + } + } + const hadKey = (0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .isArray */ .cy)(target) && (0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .isIntegerKey */ .yI)(key) ? Number(key) < target.length : (0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .hasOwn */ .$3)(target, key); + const result = Reflect.set(target, key, value, isRef(target) ? target : receiver); + if (target === toRaw(receiver)) { + if (!hadKey) { + trigger(target, "add", key, value); + } else if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .hasChanged */ .$H)(value, oldValue)) { + trigger(target, "set", key, value, oldValue); + } + } + return result; + } + deleteProperty(target, key) { + const hadKey = (0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .hasOwn */ .$3)(target, key); + const oldValue = target[key]; + const result = Reflect.deleteProperty(target, key); + if (result && hadKey) { + trigger(target, "delete", key, void 0, oldValue); + } + return result; + } + has(target, key) { + const result = Reflect.has(target, key); + if (!(0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .isSymbol */ .Bm)(key) || !builtInSymbols.has(key)) { + track(target, "has", key); + } + return result; + } + ownKeys(target) { + track(target, "iterate", (0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .isArray */ .cy)(target) ? "length" : ITERATE_KEY); + return Reflect.ownKeys(target); + } +} +class ReadonlyReactiveHandler extends BaseReactiveHandler { + constructor(isShallow2 = false) { + super(true, isShallow2); + } + set(target, key) { + if (false) // removed by dead control flow +{} + return true; + } + deleteProperty(target, key) { + if (false) // removed by dead control flow +{} + return true; + } +} +const mutableHandlers = /* @__PURE__ */new MutableReactiveHandler(); +const readonlyHandlers = /* @__PURE__ */new ReadonlyReactiveHandler(); +const shallowReactiveHandlers = /* @__PURE__ */new MutableReactiveHandler(true); +const shallowReadonlyHandlers = /* @__PURE__ */(/* unused pure expression or super */ null && (new ReadonlyReactiveHandler(true))); +const toShallow = value => value; +const getProto = v => Reflect.getPrototypeOf(v); +function createIterableMethod(method, isReadonly2, isShallow2) { + return function (...args) { + const target = this["__v_raw"]; + const rawTarget = toRaw(target); + const targetIsMap = (0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .isMap */ .CE)(rawTarget); + const isPair = method === "entries" || method === Symbol.iterator && targetIsMap; + const isKeyOnly = method === "keys" && targetIsMap; + const innerIterator = target[method](...args); + const wrap = isShallow2 ? toShallow : isReadonly2 ? toReadonly : toReactive; + !isReadonly2 && track(rawTarget, "iterate", isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY); + return { + // iterator protocol + next() { + const { + value, + done + } = innerIterator.next(); + return done ? { + value, + done + } : { + value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value), + done + }; + }, + // iterable protocol + [Symbol.iterator]() { + return this; + } + }; + }; +} +function createReadonlyMethod(type) { + return function (...args) { + if (false) // removed by dead control flow +{} + return type === "delete" ? false : type === "clear" ? void 0 : this; + }; +} +function createInstrumentations(readonly, shallow) { + const instrumentations = { + get(key) { + const target = this["__v_raw"]; + const rawTarget = toRaw(target); + const rawKey = toRaw(key); + if (!readonly) { + if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .hasChanged */ .$H)(key, rawKey)) { + track(rawTarget, "get", key); + } + track(rawTarget, "get", rawKey); + } + const { + has + } = getProto(rawTarget); + const wrap = shallow ? toShallow : readonly ? toReadonly : toReactive; + if (has.call(rawTarget, key)) { + return wrap(target.get(key)); + } else if (has.call(rawTarget, rawKey)) { + return wrap(target.get(rawKey)); + } else if (target !== rawTarget) { + target.get(key); + } + }, + get size() { + const target = this["__v_raw"]; + !readonly && track(toRaw(target), "iterate", ITERATE_KEY); + return Reflect.get(target, "size", target); + }, + has(key) { + const target = this["__v_raw"]; + const rawTarget = toRaw(target); + const rawKey = toRaw(key); + if (!readonly) { + if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .hasChanged */ .$H)(key, rawKey)) { + track(rawTarget, "has", key); + } + track(rawTarget, "has", rawKey); + } + return key === rawKey ? target.has(key) : target.has(key) || target.has(rawKey); + }, + forEach(callback, thisArg) { + const observed = this; + const target = observed["__v_raw"]; + const rawTarget = toRaw(target); + const wrap = shallow ? toShallow : readonly ? toReadonly : toReactive; + !readonly && track(rawTarget, "iterate", ITERATE_KEY); + return target.forEach((value, key) => { + return callback.call(thisArg, wrap(value), wrap(key), observed); + }); + } + }; + (0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .extend */ .X$)(instrumentations, readonly ? { + add: createReadonlyMethod("add"), + set: createReadonlyMethod("set"), + delete: createReadonlyMethod("delete"), + clear: createReadonlyMethod("clear") + } : { + add(value) { + if (!shallow && !isShallow(value) && !isReadonly(value)) { + value = toRaw(value); + } + const target = toRaw(this); + const proto = getProto(target); + const hadKey = proto.has.call(target, value); + if (!hadKey) { + target.add(value); + trigger(target, "add", value, value); + } + return this; + }, + set(key, value) { + if (!shallow && !isShallow(value) && !isReadonly(value)) { + value = toRaw(value); + } + const target = toRaw(this); + const { + has, + get + } = getProto(target); + let hadKey = has.call(target, key); + if (!hadKey) { + key = toRaw(key); + hadKey = has.call(target, key); + } else if (false) // removed by dead control flow +{} + const oldValue = get.call(target, key); + target.set(key, value); + if (!hadKey) { + trigger(target, "add", key, value); + } else if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .hasChanged */ .$H)(value, oldValue)) { + trigger(target, "set", key, value, oldValue); + } + return this; + }, + delete(key) { + const target = toRaw(this); + const { + has, + get + } = getProto(target); + let hadKey = has.call(target, key); + if (!hadKey) { + key = toRaw(key); + hadKey = has.call(target, key); + } else if (false) // removed by dead control flow +{} + const oldValue = get ? get.call(target, key) : void 0; + const result = target.delete(key); + if (hadKey) { + trigger(target, "delete", key, void 0, oldValue); + } + return result; + }, + clear() { + const target = toRaw(this); + const hadItems = target.size !== 0; + const oldTarget = false ? 0 : void 0; + const result = target.clear(); + if (hadItems) { + trigger(target, "clear", void 0, void 0, oldTarget); + } + return result; + } + }); + const iteratorMethods = ["keys", "values", "entries", Symbol.iterator]; + iteratorMethods.forEach(method => { + instrumentations[method] = createIterableMethod(method, readonly, shallow); + }); + return instrumentations; +} +function createInstrumentationGetter(isReadonly2, shallow) { + const instrumentations = createInstrumentations(isReadonly2, shallow); + return (target, key, receiver) => { + if (key === "__v_isReactive") { + return !isReadonly2; + } else if (key === "__v_isReadonly") { + return isReadonly2; + } else if (key === "__v_raw") { + return target; + } + return Reflect.get((0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .hasOwn */ .$3)(instrumentations, key) && key in target ? instrumentations : target, key, receiver); + }; +} +const mutableCollectionHandlers = { + get: /* @__PURE__ */createInstrumentationGetter(false, false) +}; +const shallowCollectionHandlers = { + get: /* @__PURE__ */createInstrumentationGetter(false, true) +}; +const readonlyCollectionHandlers = { + get: /* @__PURE__ */createInstrumentationGetter(true, false) +}; +const shallowReadonlyCollectionHandlers = (/* unused pure expression or super */ null && ({ + get: /* @__PURE__ */createInstrumentationGetter(true, true) +})); +function checkIdentityKeys(target, has, key) { + const rawKey = toRaw(key); + if (rawKey !== key && has.call(target, rawKey)) { + const type = toRawType(target); + warn(`Reactive ${type} contains both the raw and reactive versions of the same object${type === `Map` ? ` as keys` : ``}, which can lead to inconsistencies. Avoid differentiating between the raw and reactive versions of an object and only use the reactive version if possible.`); + } +} +const reactiveMap = /* @__PURE__ */new WeakMap(); +const shallowReactiveMap = /* @__PURE__ */new WeakMap(); +const readonlyMap = /* @__PURE__ */new WeakMap(); +const shallowReadonlyMap = /* @__PURE__ */new WeakMap(); +function targetTypeMap(rawType) { + switch (rawType) { + case "Object": + case "Array": + return 1 /* COMMON */; + case "Map": + case "Set": + case "WeakMap": + case "WeakSet": + return 2 /* COLLECTION */; + default: + return 0 /* INVALID */; + } +} +function getTargetType(value) { + return value["__v_skip"] || !Object.isExtensible(value) ? 0 /* INVALID */ : targetTypeMap((0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .toRawType */ .Zf)(value)); +} +function reactive(target) { + if (isReadonly(target)) { + return target; + } + return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap); +} +function shallowReactive(target) { + return createReactiveObject(target, false, shallowReactiveHandlers, shallowCollectionHandlers, shallowReactiveMap); +} +function readonly(target) { + return createReactiveObject(target, true, readonlyHandlers, readonlyCollectionHandlers, readonlyMap); +} +function shallowReadonly(target) { + return createReactiveObject(target, true, shallowReadonlyHandlers, shallowReadonlyCollectionHandlers, shallowReadonlyMap); +} +function createReactiveObject(target, isReadonly2, baseHandlers, collectionHandlers, proxyMap) { + if (!(0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .isObject */ .Gv)(target)) { + if (false) // removed by dead control flow +{} + return target; + } + if (target["__v_raw"] && !(isReadonly2 && target["__v_isReactive"])) { + return target; + } + const existingProxy = proxyMap.get(target); + if (existingProxy) { + return existingProxy; + } + const targetType = getTargetType(target); + if (targetType === 0 /* INVALID */) { + return target; + } + const proxy = new Proxy(target, targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers); + proxyMap.set(target, proxy); + return proxy; +} +function isReactive(value) { + if (isReadonly(value)) { + return isReactive(value["__v_raw"]); + } + return !!(value && value["__v_isReactive"]); +} +function isReadonly(value) { + return !!(value && value["__v_isReadonly"]); +} +function isShallow(value) { + return !!(value && value["__v_isShallow"]); +} +function isProxy(value) { + return value ? !!value["__v_raw"] : false; +} +function toRaw(observed) { + const raw = observed && observed["__v_raw"]; + return raw ? toRaw(raw) : observed; +} +function markRaw(value) { + if (!(0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .hasOwn */ .$3)(value, "__v_skip") && Object.isExtensible(value)) { + (0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .def */ .yQ)(value, "__v_skip", true); + } + return value; +} +const toReactive = value => (0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .isObject */ .Gv)(value) ? reactive(value) : value; +const toReadonly = value => (0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .isObject */ .Gv)(value) ? readonly(value) : value; +function isRef(r) { + return r ? r["__v_isRef"] === true : false; +} +function ref(value) { + return createRef(value, false); +} +function shallowRef(value) { + return createRef(value, true); +} +function createRef(rawValue, shallow) { + if (isRef(rawValue)) { + return rawValue; + } + return new RefImpl(rawValue, shallow); +} +class RefImpl { + constructor(value, isShallow2) { + this.dep = new Dep(); + this["__v_isRef"] = true; + this["__v_isShallow"] = false; + this._rawValue = isShallow2 ? value : toRaw(value); + this._value = isShallow2 ? value : toReactive(value); + this["__v_isShallow"] = isShallow2; + } + get value() { + if (false) // removed by dead control flow +{} else { + this.dep.track(); + } + return this._value; + } + set value(newValue) { + const oldValue = this._rawValue; + const useDirectValue = this["__v_isShallow"] || isShallow(newValue) || isReadonly(newValue); + newValue = useDirectValue ? newValue : toRaw(newValue); + if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .hasChanged */ .$H)(newValue, oldValue)) { + this._rawValue = newValue; + this._value = useDirectValue ? newValue : toReactive(newValue); + if (false) // removed by dead control flow +{} else { + this.dep.trigger(); + } + } + } +} +function triggerRef(ref2) { + if (ref2.dep) { + if (false) // removed by dead control flow +{} else { + ref2.dep.trigger(); + } + } +} +function unref(ref2) { + return isRef(ref2) ? ref2.value : ref2; +} +function toValue(source) { + return isFunction(source) ? source() : unref(source); +} +const shallowUnwrapHandlers = { + get: (target, key, receiver) => key === "__v_raw" ? target : unref(Reflect.get(target, key, receiver)), + set: (target, key, value, receiver) => { + const oldValue = target[key]; + if (isRef(oldValue) && !isRef(value)) { + oldValue.value = value; + return true; + } else { + return Reflect.set(target, key, value, receiver); + } + } +}; +function proxyRefs(objectWithRefs) { + return isReactive(objectWithRefs) ? objectWithRefs : new Proxy(objectWithRefs, shallowUnwrapHandlers); +} +class CustomRefImpl { + constructor(factory) { + this["__v_isRef"] = true; + this._value = void 0; + const dep = this.dep = new Dep(); + const { + get, + set + } = factory(dep.track.bind(dep), dep.trigger.bind(dep)); + this._get = get; + this._set = set; + } + get value() { + return this._value = this._get(); + } + set value(newVal) { + this._set(newVal); + } +} +function customRef(factory) { + return new CustomRefImpl(factory); +} +function toRefs(object) { + if (false) // removed by dead control flow +{} + const ret = (0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .isArray */ .cy)(object) ? new Array(object.length) : {}; + for (const key in object) { + ret[key] = propertyToRef(object, key); + } + return ret; +} +class ObjectRefImpl { + constructor(_object, _key, _defaultValue) { + this._object = _object; + this._key = _key; + this._defaultValue = _defaultValue; + this["__v_isRef"] = true; + this._value = void 0; + } + get value() { + const val = this._object[this._key]; + return this._value = val === void 0 ? this._defaultValue : val; + } + set value(newVal) { + this._object[this._key] = newVal; + } + get dep() { + return getDepFromReactive(toRaw(this._object), this._key); + } +} +class GetterRefImpl { + constructor(_getter) { + this._getter = _getter; + this["__v_isRef"] = true; + this["__v_isReadonly"] = true; + this._value = void 0; + } + get value() { + return this._value = this._getter(); + } +} +function toRef(source, key, defaultValue) { + if (isRef(source)) { + return source; + } else if (isFunction(source)) { + return new GetterRefImpl(source); + } else if (isObject(source) && arguments.length > 1) { + return propertyToRef(source, key, defaultValue); + } else { + return ref(source); + } +} +function propertyToRef(source, key, defaultValue) { + const val = source[key]; + return isRef(val) ? val : new ObjectRefImpl(source, key, defaultValue); +} +class ComputedRefImpl { + constructor(fn, setter, isSSR) { + this.fn = fn; + this.setter = setter; + /** + * @internal + */ + this._value = void 0; + /** + * @internal + */ + this.dep = new Dep(this); + /** + * @internal + */ + this.__v_isRef = true; + // TODO isolatedDeclarations "__v_isReadonly" + // A computed is also a subscriber that tracks other deps + /** + * @internal + */ + this.deps = void 0; + /** + * @internal + */ + this.depsTail = void 0; + /** + * @internal + */ + this.flags = 16; + /** + * @internal + */ + this.globalVersion = globalVersion - 1; + /** + * @internal + */ + this.next = void 0; + // for backwards compat + this.effect = this; + this["__v_isReadonly"] = !setter; + this.isSSR = isSSR; + } + /** + * @internal + */ + notify() { + this.flags |= 16; + if (!(this.flags & 8) && + // avoid infinite self recursion + activeSub !== this) { + batch(this, true); + return true; + } else if (false) // removed by dead control flow +{} + } + get value() { + const link = false ? 0 : this.dep.track(); + refreshComputed(this); + if (link) { + link.version = this.dep.version; + } + return this._value; + } + set value(newValue) { + if (this.setter) { + this.setter(newValue); + } else if (false) // removed by dead control flow +{} + } +} +function computed(getterOrOptions, debugOptions, isSSR = false) { + let getter; + let setter; + if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .isFunction */ .Tn)(getterOrOptions)) { + getter = getterOrOptions; + } else { + getter = getterOrOptions.get; + setter = getterOrOptions.set; + } + const cRef = new ComputedRefImpl(getter, setter, isSSR); + if (false) // removed by dead control flow +{} + return cRef; +} +const TrackOpTypes = (/* unused pure expression or super */ null && ({ + "GET": "get", + "HAS": "has", + "ITERATE": "iterate" +})); +const TriggerOpTypes = (/* unused pure expression or super */ null && ({ + "SET": "set", + "ADD": "add", + "DELETE": "delete", + "CLEAR": "clear" +})); +const ReactiveFlags = (/* unused pure expression or super */ null && ({ + "SKIP": "__v_skip", + "IS_REACTIVE": "__v_isReactive", + "IS_READONLY": "__v_isReadonly", + "IS_SHALLOW": "__v_isShallow", + "RAW": "__v_raw", + "IS_REF": "__v_isRef" +})); +const WatchErrorCodes = (/* unused pure expression or super */ null && ({ + "WATCH_GETTER": 2, + "2": "WATCH_GETTER", + "WATCH_CALLBACK": 3, + "3": "WATCH_CALLBACK", + "WATCH_CLEANUP": 4, + "4": "WATCH_CLEANUP" +})); +const INITIAL_WATCHER_VALUE = {}; +const cleanupMap = /* @__PURE__ */new WeakMap(); +let activeWatcher = void 0; +function getCurrentWatcher() { + return activeWatcher; +} +function onWatcherCleanup(cleanupFn, failSilently = false, owner = activeWatcher) { + if (owner) { + let cleanups = cleanupMap.get(owner); + if (!cleanups) cleanupMap.set(owner, cleanups = []); + cleanups.push(cleanupFn); + } else if (false) // removed by dead control flow +{} +} +function watch(source, cb, options = _vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .EMPTY_OBJ */ .MZ) { + const { + immediate, + deep, + once, + scheduler, + augmentJob, + call + } = options; + const warnInvalidSource = s => { + (options.onWarn || warn)(`Invalid watch source: `, s, `A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types.`); + }; + const reactiveGetter = source2 => { + if (deep) return source2; + if (isShallow(source2) || deep === false || deep === 0) return traverse(source2, 1); + return traverse(source2); + }; + let effect; + let getter; + let cleanup; + let boundCleanup; + let forceTrigger = false; + let isMultiSource = false; + if (isRef(source)) { + getter = () => source.value; + forceTrigger = isShallow(source); + } else if (isReactive(source)) { + getter = () => reactiveGetter(source); + forceTrigger = true; + } else if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .isArray */ .cy)(source)) { + isMultiSource = true; + forceTrigger = source.some(s => isReactive(s) || isShallow(s)); + getter = () => source.map(s => { + if (isRef(s)) { + return s.value; + } else if (isReactive(s)) { + return reactiveGetter(s); + } else if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .isFunction */ .Tn)(s)) { + return call ? call(s, 2) : s(); + } else { + false && 0; + } + }); + } else if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .isFunction */ .Tn)(source)) { + if (cb) { + getter = call ? () => call(source, 2) : source; + } else { + getter = () => { + if (cleanup) { + pauseTracking(); + try { + cleanup(); + } finally { + resetTracking(); + } + } + const currentEffect = activeWatcher; + activeWatcher = effect; + try { + return call ? call(source, 3, [boundCleanup]) : source(boundCleanup); + } finally { + activeWatcher = currentEffect; + } + }; + } + } else { + getter = _vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .NOOP */ .tE; + false && 0; + } + if (cb && deep) { + const baseGetter = getter; + const depth = deep === true ? Infinity : deep; + getter = () => traverse(baseGetter(), depth); + } + const scope = getCurrentScope(); + const watchHandle = () => { + effect.stop(); + if (scope && scope.active) { + (0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .remove */ .TF)(scope.effects, effect); + } + }; + if (once && cb) { + const _cb = cb; + cb = (...args) => { + _cb(...args); + watchHandle(); + }; + } + let oldValue = isMultiSource ? new Array(source.length).fill(INITIAL_WATCHER_VALUE) : INITIAL_WATCHER_VALUE; + const job = immediateFirstRun => { + if (!(effect.flags & 1) || !effect.dirty && !immediateFirstRun) { + return; + } + if (cb) { + const newValue = effect.run(); + if (deep || forceTrigger || (isMultiSource ? newValue.some((v, i) => (0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .hasChanged */ .$H)(v, oldValue[i])) : (0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .hasChanged */ .$H)(newValue, oldValue))) { + if (cleanup) { + cleanup(); + } + const currentWatcher = activeWatcher; + activeWatcher = effect; + try { + const args = [newValue, + // pass undefined as the old value when it's changed for the first time + oldValue === INITIAL_WATCHER_VALUE ? void 0 : isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE ? [] : oldValue, boundCleanup]; + call ? call(cb, 3, args) : + // @ts-expect-error + cb(...args); + oldValue = newValue; + } finally { + activeWatcher = currentWatcher; + } + } + } else { + effect.run(); + } + }; + if (augmentJob) { + augmentJob(job); + } + effect = new ReactiveEffect(getter); + effect.scheduler = scheduler ? () => scheduler(job, false) : job; + boundCleanup = fn => onWatcherCleanup(fn, false, effect); + cleanup = effect.onStop = () => { + const cleanups = cleanupMap.get(effect); + if (cleanups) { + if (call) { + call(cleanups, 4); + } else { + for (const cleanup2 of cleanups) cleanup2(); + } + cleanupMap.delete(effect); + } + }; + if (false) // removed by dead control flow +{} + if (cb) { + if (immediate) { + job(true); + } else { + oldValue = effect.run(); + } + } else if (scheduler) { + scheduler(job.bind(null, true), true); + } else { + effect.run(); + } + watchHandle.pause = effect.pause.bind(effect); + watchHandle.resume = effect.resume.bind(effect); + watchHandle.stop = watchHandle; + return watchHandle; +} +function traverse(value, depth = Infinity, seen) { + if (depth <= 0 || !(0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .isObject */ .Gv)(value) || value["__v_skip"]) { + return value; + } + seen = seen || /* @__PURE__ */new Set(); + if (seen.has(value)) { + return value; + } + seen.add(value); + depth--; + if (isRef(value)) { + traverse(value.value, depth, seen); + } else if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .isArray */ .cy)(value)) { + for (let i = 0; i < value.length; i++) { + traverse(value[i], depth, seen); + } + } else if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .isSet */ .vM)(value) || (0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .isMap */ .CE)(value)) { + value.forEach(v => { + traverse(v, depth, seen); + }); + } else if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_17__/* .isPlainObject */ .Qd)(value)) { + for (const key in value) { + traverse(value[key], depth, seen); + } + for (const key of Object.getOwnPropertySymbols(value)) { + if (Object.prototype.propertyIsEnumerable.call(value, key)) { + traverse(value[key], depth, seen); + } + } + } + return value; +} + + +/***/ }), + +/***/ 6107: +/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +"use strict"; +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ pD: function() { return /* binding */ makeMap; } +/* harmony export */ }); +/* unused harmony exports EMPTY_ARR, NO, PatchFlagNames, PatchFlags, ShapeFlags, SlotFlags, camelize, capitalize, cssVarNameEscapeSymbolsRE, escapeHtml, escapeHtmlComment, genCacheKey, genPropsAccessExp, generateCodeFrame, getEscapedCssVarName, getGlobalThis, hyphenate, includeBooleanAttr, invokeArrayFns, isBooleanAttr, isBuiltInDirective, isDate, isGloballyAllowed, isGloballyWhitelisted, isHTMLTag, isKnownHtmlAttr, isKnownMathMLAttr, isKnownSvgAttr, isMathMLTag, isModelListener, isOn, isPromise, isRegExp, isRenderableAttrValue, isReservedProp, isSSRSafeAttrName, isSVGTag, isSpecialBooleanAttr, isString, isVoidTag, looseEqual, looseIndexOf, looseToNumber, normalizeClass, normalizeProps, normalizeStyle, objectToString, parseStringStyle, propsToAttrMap, slotFlagsText, stringifyStyle, toDisplayString, toHandlerKey, toNumber, toTypeString */ +/* harmony import */ var core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4114); +/* harmony import */ var core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var core_js_modules_es_iterator_constructor_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(8111); +/* harmony import */ var core_js_modules_es_iterator_constructor_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_constructor_js__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var core_js_modules_es_iterator_filter_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(2489); +/* harmony import */ var core_js_modules_es_iterator_filter_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_filter_js__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var core_js_modules_es_iterator_for_each_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(7588); +/* harmony import */ var core_js_modules_es_iterator_for_each_js__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_for_each_js__WEBPACK_IMPORTED_MODULE_3__); + + + + +/** +* @vue/shared v3.5.13 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/ +/*! #__NO_SIDE_EFFECTS__ */ +// @__NO_SIDE_EFFECTS__ +function makeMap(str) { + const map = /* @__PURE__ */Object.create(null); + for (const key of str.split(",")) map[key] = 1; + return val => val in map; +} +const EMPTY_OBJ = false ? 0 : {}; +const EMPTY_ARR = false ? 0 : []; +const NOOP = () => {}; +const NO = () => false; +const isOn = key => key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110 && ( +// uppercase letter +key.charCodeAt(2) > 122 || key.charCodeAt(2) < 97); +const isModelListener = key => key.startsWith("onUpdate:"); +const extend = Object.assign; +const remove = (arr, el) => { + const i = arr.indexOf(el); + if (i > -1) { + arr.splice(i, 1); + } +}; +const hasOwnProperty = Object.prototype.hasOwnProperty; +const hasOwn = (val, key) => hasOwnProperty.call(val, key); +const isArray = Array.isArray; +const isMap = val => toTypeString(val) === "[object Map]"; +const isSet = val => toTypeString(val) === "[object Set]"; +const isDate = val => toTypeString(val) === "[object Date]"; +const isRegExp = val => toTypeString(val) === "[object RegExp]"; +const isFunction = val => typeof val === "function"; +const isString = val => typeof val === "string"; +const isSymbol = val => typeof val === "symbol"; +const isObject = val => val !== null && typeof val === "object"; +const isPromise = val => { + return (isObject(val) || isFunction(val)) && isFunction(val.then) && isFunction(val.catch); +}; +const objectToString = Object.prototype.toString; +const toTypeString = value => objectToString.call(value); +const toRawType = value => { + return toTypeString(value).slice(8, -1); +}; +const isPlainObject = val => toTypeString(val) === "[object Object]"; +const isIntegerKey = key => isString(key) && key !== "NaN" && key[0] !== "-" && "" + parseInt(key, 10) === key; +const isReservedProp = /* @__PURE__ */(/* unused pure expression or super */ null && (makeMap( +// the leading comma is intentional so empty string "" is also included +",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"))); +const isBuiltInDirective = /* @__PURE__ */(/* unused pure expression or super */ null && (makeMap("bind,cloak,else-if,else,for,html,if,model,on,once,pre,show,slot,text,memo"))); +const cacheStringFunction = fn => { + const cache = /* @__PURE__ */Object.create(null); + return str => { + const hit = cache[str]; + return hit || (cache[str] = fn(str)); + }; +}; +const camelizeRE = /-(\w)/g; +const camelize = cacheStringFunction(str => { + return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : ""); +}); +const hyphenateRE = /\B([A-Z])/g; +const hyphenate = cacheStringFunction(str => str.replace(hyphenateRE, "-$1").toLowerCase()); +const capitalize = cacheStringFunction(str => { + return str.charAt(0).toUpperCase() + str.slice(1); +}); +const toHandlerKey = cacheStringFunction(str => { + const s = str ? `on${capitalize(str)}` : ``; + return s; +}); +const hasChanged = (value, oldValue) => !Object.is(value, oldValue); +const invokeArrayFns = (fns, ...arg) => { + for (let i = 0; i < fns.length; i++) { + fns[i](...arg); + } +}; +const def = (obj, key, value, writable = false) => { + Object.defineProperty(obj, key, { + configurable: true, + enumerable: false, + writable, + value + }); +}; +const looseToNumber = val => { + const n = parseFloat(val); + return isNaN(n) ? val : n; +}; +const toNumber = val => { + const n = isString(val) ? Number(val) : NaN; + return isNaN(n) ? val : n; +}; +let _globalThis; +const getGlobalThis = () => { + return _globalThis || (_globalThis = typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : typeof __webpack_require__.g !== "undefined" ? __webpack_require__.g : {}); +}; +const identRE = /^[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*$/; +function genPropsAccessExp(name) { + return identRE.test(name) ? `__props.${name}` : `__props[${JSON.stringify(name)}]`; +} +function genCacheKey(source, options) { + return source + JSON.stringify(options, (_, val) => typeof val === "function" ? val.toString() : val); +} +const PatchFlags = (/* unused pure expression or super */ null && ({ + "TEXT": 1, + "1": "TEXT", + "CLASS": 2, + "2": "CLASS", + "STYLE": 4, + "4": "STYLE", + "PROPS": 8, + "8": "PROPS", + "FULL_PROPS": 16, + "16": "FULL_PROPS", + "NEED_HYDRATION": 32, + "32": "NEED_HYDRATION", + "STABLE_FRAGMENT": 64, + "64": "STABLE_FRAGMENT", + "KEYED_FRAGMENT": 128, + "128": "KEYED_FRAGMENT", + "UNKEYED_FRAGMENT": 256, + "256": "UNKEYED_FRAGMENT", + "NEED_PATCH": 512, + "512": "NEED_PATCH", + "DYNAMIC_SLOTS": 1024, + "1024": "DYNAMIC_SLOTS", + "DEV_ROOT_FRAGMENT": 2048, + "2048": "DEV_ROOT_FRAGMENT", + "CACHED": -1, + "-1": "CACHED", + "BAIL": -2, + "-2": "BAIL" +})); +const PatchFlagNames = (/* unused pure expression or super */ null && ({ + [1]: `TEXT`, + [2]: `CLASS`, + [4]: `STYLE`, + [8]: `PROPS`, + [16]: `FULL_PROPS`, + [32]: `NEED_HYDRATION`, + [64]: `STABLE_FRAGMENT`, + [128]: `KEYED_FRAGMENT`, + [256]: `UNKEYED_FRAGMENT`, + [512]: `NEED_PATCH`, + [1024]: `DYNAMIC_SLOTS`, + [2048]: `DEV_ROOT_FRAGMENT`, + [-1]: `HOISTED`, + [-2]: `BAIL` +})); +const ShapeFlags = (/* unused pure expression or super */ null && ({ + "ELEMENT": 1, + "1": "ELEMENT", + "FUNCTIONAL_COMPONENT": 2, + "2": "FUNCTIONAL_COMPONENT", + "STATEFUL_COMPONENT": 4, + "4": "STATEFUL_COMPONENT", + "TEXT_CHILDREN": 8, + "8": "TEXT_CHILDREN", + "ARRAY_CHILDREN": 16, + "16": "ARRAY_CHILDREN", + "SLOTS_CHILDREN": 32, + "32": "SLOTS_CHILDREN", + "TELEPORT": 64, + "64": "TELEPORT", + "SUSPENSE": 128, + "128": "SUSPENSE", + "COMPONENT_SHOULD_KEEP_ALIVE": 256, + "256": "COMPONENT_SHOULD_KEEP_ALIVE", + "COMPONENT_KEPT_ALIVE": 512, + "512": "COMPONENT_KEPT_ALIVE", + "COMPONENT": 6, + "6": "COMPONENT" +})); +const SlotFlags = (/* unused pure expression or super */ null && ({ + "STABLE": 1, + "1": "STABLE", + "DYNAMIC": 2, + "2": "DYNAMIC", + "FORWARDED": 3, + "3": "FORWARDED" +})); +const slotFlagsText = (/* unused pure expression or super */ null && ({ + [1]: "STABLE", + [2]: "DYNAMIC", + [3]: "FORWARDED" +})); +const GLOBALS_ALLOWED = "Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt,console,Error,Symbol"; +const isGloballyAllowed = /* @__PURE__ */(/* unused pure expression or super */ null && (makeMap(GLOBALS_ALLOWED))); +const isGloballyWhitelisted = (/* unused pure expression or super */ null && (isGloballyAllowed)); +const range = 2; +function generateCodeFrame(source, start = 0, end = source.length) { + start = Math.max(0, Math.min(start, source.length)); + end = Math.max(0, Math.min(end, source.length)); + if (start > end) return ""; + let lines = source.split(/(\r?\n)/); + const newlineSequences = lines.filter((_, idx) => idx % 2 === 1); + lines = lines.filter((_, idx) => idx % 2 === 0); + let count = 0; + const res = []; + for (let i = 0; i < lines.length; i++) { + count += lines[i].length + (newlineSequences[i] && newlineSequences[i].length || 0); + if (count >= start) { + for (let j = i - range; j <= i + range || end > count; j++) { + if (j < 0 || j >= lines.length) continue; + const line = j + 1; + res.push(`${line}${" ".repeat(Math.max(3 - String(line).length, 0))}| ${lines[j]}`); + const lineLength = lines[j].length; + const newLineSeqLength = newlineSequences[j] && newlineSequences[j].length || 0; + if (j === i) { + const pad = start - (count - (lineLength + newLineSeqLength)); + const length = Math.max(1, end > count ? lineLength - pad : end - start); + res.push(` | ` + " ".repeat(pad) + "^".repeat(length)); + } else if (j > i) { + if (end > count) { + const length = Math.max(Math.min(end - count, lineLength), 1); + res.push(` | ` + "^".repeat(length)); + } + count += lineLength + newLineSeqLength; + } + } + break; + } + } + return res.join("\n"); +} +function normalizeStyle(value) { + if (isArray(value)) { + const res = {}; + for (let i = 0; i < value.length; i++) { + const item = value[i]; + const normalized = isString(item) ? parseStringStyle(item) : normalizeStyle(item); + if (normalized) { + for (const key in normalized) { + res[key] = normalized[key]; + } + } + } + return res; + } else if (isString(value) || isObject(value)) { + return value; + } +} +const listDelimiterRE = /;(?![^(]*\))/g; +const propertyDelimiterRE = /:([^]+)/; +const styleCommentRE = /\/\*[^]*?\*\//g; +function parseStringStyle(cssText) { + const ret = {}; + cssText.replace(styleCommentRE, "").split(listDelimiterRE).forEach(item => { + if (item) { + const tmp = item.split(propertyDelimiterRE); + tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim()); + } + }); + return ret; +} +function stringifyStyle(styles) { + if (!styles) return ""; + if (isString(styles)) return styles; + let ret = ""; + for (const key in styles) { + const value = styles[key]; + if (isString(value) || typeof value === "number") { + const normalizedKey = key.startsWith(`--`) ? key : hyphenate(key); + ret += `${normalizedKey}:${value};`; + } + } + return ret; +} +function normalizeClass(value) { + let res = ""; + if (isString(value)) { + res = value; + } else if (isArray(value)) { + for (let i = 0; i < value.length; i++) { + const normalized = normalizeClass(value[i]); + if (normalized) { + res += normalized + " "; + } + } + } else if (isObject(value)) { + for (const name in value) { + if (value[name]) { + res += name + " "; + } + } + } + return res.trim(); +} +function normalizeProps(props) { + if (!props) return null; + let { + class: klass, + style + } = props; + if (klass && !isString(klass)) { + props.class = normalizeClass(klass); + } + if (style) { + props.style = normalizeStyle(style); + } + return props; +} +const HTML_TAGS = "html,body,base,head,link,meta,style,title,address,article,aside,footer,header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,output,progress,select,textarea,details,dialog,menu,summary,template,blockquote,iframe,tfoot"; +const SVG_TAGS = "svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,feDistantLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,text,textPath,title,tspan,unknown,use,view"; +const MATH_TAGS = "annotation,annotation-xml,maction,maligngroup,malignmark,math,menclose,merror,mfenced,mfrac,mfraction,mglyph,mi,mlabeledtr,mlongdiv,mmultiscripts,mn,mo,mover,mpadded,mphantom,mprescripts,mroot,mrow,ms,mscarries,mscarry,msgroup,msline,mspace,msqrt,msrow,mstack,mstyle,msub,msubsup,msup,mtable,mtd,mtext,mtr,munder,munderover,none,semantics"; +const VOID_TAGS = "area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr"; +const isHTMLTag = /* @__PURE__ */(/* unused pure expression or super */ null && (makeMap(HTML_TAGS))); +const isSVGTag = /* @__PURE__ */(/* unused pure expression or super */ null && (makeMap(SVG_TAGS))); +const isMathMLTag = /* @__PURE__ */(/* unused pure expression or super */ null && (makeMap(MATH_TAGS))); +const isVoidTag = /* @__PURE__ */(/* unused pure expression or super */ null && (makeMap(VOID_TAGS))); +const specialBooleanAttrs = `itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly`; +const isSpecialBooleanAttr = /* @__PURE__ */(/* unused pure expression or super */ null && (makeMap(specialBooleanAttrs))); +const isBooleanAttr = /* @__PURE__ */makeMap(specialBooleanAttrs + `,async,autofocus,autoplay,controls,default,defer,disabled,hidden,inert,loop,open,required,reversed,scoped,seamless,checked,muted,multiple,selected`); +function includeBooleanAttr(value) { + return !!value || value === ""; +} +const unsafeAttrCharRE = /[>/="'\u0009\u000a\u000c\u0020]/; +const attrValidationCache = (/* unused pure expression or super */ null && ({})); +function isSSRSafeAttrName(name) { + if (attrValidationCache.hasOwnProperty(name)) { + return attrValidationCache[name]; + } + const isUnsafe = unsafeAttrCharRE.test(name); + if (isUnsafe) { + console.error(`unsafe attribute name: ${name}`); + } + return attrValidationCache[name] = !isUnsafe; +} +const propsToAttrMap = (/* unused pure expression or super */ null && ({ + acceptCharset: "accept-charset", + className: "class", + htmlFor: "for", + httpEquiv: "http-equiv" +})); +const isKnownHtmlAttr = /* @__PURE__ */(/* unused pure expression or super */ null && (makeMap(`accept,accept-charset,accesskey,action,align,allow,alt,async,autocapitalize,autocomplete,autofocus,autoplay,background,bgcolor,border,buffered,capture,challenge,charset,checked,cite,class,code,codebase,color,cols,colspan,content,contenteditable,contextmenu,controls,coords,crossorigin,csp,data,datetime,decoding,default,defer,dir,dirname,disabled,download,draggable,dropzone,enctype,enterkeyhint,for,form,formaction,formenctype,formmethod,formnovalidate,formtarget,headers,height,hidden,high,href,hreflang,http-equiv,icon,id,importance,inert,integrity,ismap,itemprop,keytype,kind,label,lang,language,loading,list,loop,low,manifest,max,maxlength,minlength,media,min,multiple,muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,preload,radiogroup,readonly,referrerpolicy,rel,required,reversed,rows,rowspan,sandbox,scope,scoped,selected,shape,size,sizes,slot,span,spellcheck,src,srcdoc,srclang,srcset,start,step,style,summary,tabindex,target,title,translate,type,usemap,value,width,wrap`))); +const isKnownSvgAttr = /* @__PURE__ */(/* unused pure expression or super */ null && (makeMap(`xmlns,accent-height,accumulate,additive,alignment-baseline,alphabetic,amplitude,arabic-form,ascent,attributeName,attributeType,azimuth,baseFrequency,baseline-shift,baseProfile,bbox,begin,bias,by,calcMode,cap-height,class,clip,clipPathUnits,clip-path,clip-rule,color,color-interpolation,color-interpolation-filters,color-profile,color-rendering,contentScriptType,contentStyleType,crossorigin,cursor,cx,cy,d,decelerate,descent,diffuseConstant,direction,display,divisor,dominant-baseline,dur,dx,dy,edgeMode,elevation,enable-background,end,exponent,fill,fill-opacity,fill-rule,filter,filterRes,filterUnits,flood-color,flood-opacity,font-family,font-size,font-size-adjust,font-stretch,font-style,font-variant,font-weight,format,from,fr,fx,fy,g1,g2,glyph-name,glyph-orientation-horizontal,glyph-orientation-vertical,glyphRef,gradientTransform,gradientUnits,hanging,height,href,hreflang,horiz-adv-x,horiz-origin-x,id,ideographic,image-rendering,in,in2,intercept,k,k1,k2,k3,k4,kernelMatrix,kernelUnitLength,kerning,keyPoints,keySplines,keyTimes,lang,lengthAdjust,letter-spacing,lighting-color,limitingConeAngle,local,marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mask,maskContentUnits,maskUnits,mathematical,max,media,method,min,mode,name,numOctaves,offset,opacity,operator,order,orient,orientation,origin,overflow,overline-position,overline-thickness,panose-1,paint-order,path,pathLength,patternContentUnits,patternTransform,patternUnits,ping,pointer-events,points,pointsAtX,pointsAtY,pointsAtZ,preserveAlpha,preserveAspectRatio,primitiveUnits,r,radius,referrerPolicy,refX,refY,rel,rendering-intent,repeatCount,repeatDur,requiredExtensions,requiredFeatures,restart,result,rotate,rx,ry,scale,seed,shape-rendering,slope,spacing,specularConstant,specularExponent,speed,spreadMethod,startOffset,stdDeviation,stemh,stemv,stitchTiles,stop-color,stop-opacity,strikethrough-position,strikethrough-thickness,string,stroke,stroke-dasharray,stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,stroke-width,style,surfaceScale,systemLanguage,tabindex,tableValues,target,targetX,targetY,text-anchor,text-decoration,text-rendering,textLength,to,transform,transform-origin,type,u1,u2,underline-position,underline-thickness,unicode,unicode-bidi,unicode-range,units-per-em,v-alphabetic,v-hanging,v-ideographic,v-mathematical,values,vector-effect,version,vert-adv-y,vert-origin-x,vert-origin-y,viewBox,viewTarget,visibility,width,widths,word-spacing,writing-mode,x,x-height,x1,x2,xChannelSelector,xlink:actuate,xlink:arcrole,xlink:href,xlink:role,xlink:show,xlink:title,xlink:type,xmlns:xlink,xml:base,xml:lang,xml:space,y,y1,y2,yChannelSelector,z,zoomAndPan`))); +const isKnownMathMLAttr = /* @__PURE__ */(/* unused pure expression or super */ null && (makeMap(`accent,accentunder,actiontype,align,alignmentscope,altimg,altimg-height,altimg-valign,altimg-width,alttext,bevelled,close,columnsalign,columnlines,columnspan,denomalign,depth,dir,display,displaystyle,encoding,equalcolumns,equalrows,fence,fontstyle,fontweight,form,frame,framespacing,groupalign,height,href,id,indentalign,indentalignfirst,indentalignlast,indentshift,indentshiftfirst,indentshiftlast,indextype,justify,largetop,largeop,lquote,lspace,mathbackground,mathcolor,mathsize,mathvariant,maxsize,minlabelspacing,mode,other,overflow,position,rowalign,rowlines,rowspan,rquote,rspace,scriptlevel,scriptminsize,scriptsizemultiplier,selection,separator,separators,shift,side,src,stackalign,stretchy,subscriptshift,superscriptshift,symmetric,voffset,width,widths,xlink:href,xlink:show,xlink:type,xmlns`))); +function isRenderableAttrValue(value) { + if (value == null) { + return false; + } + const type = typeof value; + return type === "string" || type === "number" || type === "boolean"; +} +const escapeRE = /["'&<>]/; +function escapeHtml(string) { + const str = "" + string; + const match = escapeRE.exec(str); + if (!match) { + return str; + } + let html = ""; + let escaped; + let index; + let lastIndex = 0; + for (index = match.index; index < str.length; index++) { + switch (str.charCodeAt(index)) { + case 34: + escaped = """; + break; + case 38: + escaped = "&"; + break; + case 39: + escaped = "'"; + break; + case 60: + escaped = "<"; + break; + case 62: + escaped = ">"; + break; + default: + continue; + } + if (lastIndex !== index) { + html += str.slice(lastIndex, index); + } + lastIndex = index + 1; + html += escaped; + } + return lastIndex !== index ? html + str.slice(lastIndex, index) : html; +} +const commentStripRE = /^-?>||--!>|?@[\\\]^`{|}~]/g; +function getEscapedCssVarName(key, doubleEscape) { + return key.replace(cssVarNameEscapeSymbolsRE, s => doubleEscape ? s === '"' ? '\\\\\\"' : `\\\\${s}` : `\\${s}`); +} +function looseCompareArrays(a, b) { + if (a.length !== b.length) return false; + let equal = true; + for (let i = 0; equal && i < a.length; i++) { + equal = looseEqual(a[i], b[i]); + } + return equal; +} +function looseEqual(a, b) { + if (a === b) return true; + let aValidType = isDate(a); + let bValidType = isDate(b); + if (aValidType || bValidType) { + return aValidType && bValidType ? a.getTime() === b.getTime() : false; + } + aValidType = isSymbol(a); + bValidType = isSymbol(b); + if (aValidType || bValidType) { + return a === b; + } + aValidType = isArray(a); + bValidType = isArray(b); + if (aValidType || bValidType) { + return aValidType && bValidType ? looseCompareArrays(a, b) : false; + } + aValidType = isObject(a); + bValidType = isObject(b); + if (aValidType || bValidType) { + if (!aValidType || !bValidType) { + return false; + } + const aKeysCount = Object.keys(a).length; + const bKeysCount = Object.keys(b).length; + if (aKeysCount !== bKeysCount) { + return false; + } + for (const key in a) { + const aHasKey = a.hasOwnProperty(key); + const bHasKey = b.hasOwnProperty(key); + if (aHasKey && !bHasKey || !aHasKey && bHasKey || !looseEqual(a[key], b[key])) { + return false; + } + } + } + return String(a) === String(b); +} +function looseIndexOf(arr, val) { + return arr.findIndex(item => looseEqual(item, val)); +} +const isRef = val => { + return !!(val && val["__v_isRef"] === true); +}; +const toDisplayString = val => { + return isString(val) ? val : val == null ? "" : isArray(val) || isObject(val) && (val.toString === objectToString || !isFunction(val.toString)) ? isRef(val) ? toDisplayString(val.value) : JSON.stringify(val, replacer, 2) : String(val); +}; +const replacer = (_key, val) => { + if (isRef(val)) { + return replacer(_key, val.value); + } else if (isMap(val)) { + return { + [`Map(${val.size})`]: [...val.entries()].reduce((entries, [key, val2], i) => { + entries[stringifySymbol(key, i) + " =>"] = val2; + return entries; + }, {}) + }; + } else if (isSet(val)) { + return { + [`Set(${val.size})`]: [...val.values()].map(v => stringifySymbol(v)) + }; + } else if (isSymbol(val)) { + return stringifySymbol(val); + } else if (isObject(val) && !isArray(val) && !isPlainObject(val)) { + return String(val); + } + return val; +}; +const stringifySymbol = (v, i = "") => { + var _a; + return ( + // Symbol.description in es2019+ so we need to cast here to pass + // the lib: es2016 check + isSymbol(v) ? `Symbol(${(_a = v.description) != null ? _a : i})` : v + ); +}; + +/* harmony export */ __webpack_require__.d(__webpack_exports__, [ +/* harmony export */ "$3", 0, /* binding */ hasOwn, +/* harmony export */ "$H", 0, /* binding */ hasChanged, +/* harmony export */ "Bm", 0, /* binding */ isSymbol, +/* harmony export */ "CE", 0, /* binding */ isMap, +/* harmony export */ "Gv", 0, /* binding */ isObject, +/* harmony export */ "MZ", 0, /* binding */ EMPTY_OBJ, +/* harmony export */ "Qd", 0, /* binding */ isPlainObject, +/* harmony export */ "TF", 0, /* binding */ remove, +/* harmony export */ "Tn", 0, /* binding */ isFunction, +/* harmony export */ "X$", 0, /* binding */ extend, +/* harmony export */ "Zf", 0, /* binding */ toRawType, +/* harmony export */ "cy", 0, /* binding */ isArray, +/* harmony export */ "tE", 0, /* binding */ NOOP, +/* harmony export */ "vM", 0, /* binding */ isSet, +/* harmony export */ "yI", 0, /* binding */ isIntegerKey, +/* harmony export */ "yQ", 0, /* binding */ def +/* harmony export */ ]); + + +/***/ }), + +/***/ 6768: +/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +"use strict"; +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ CE: function() { return /* binding */ createElementBlock; }, +/* harmony export */ Fv: function() { return /* binding */ createStaticVNode; }, +/* harmony export */ K9: function() { return /* binding */ createRenderer; }, +/* harmony export */ Lk: function() { return /* binding */ createBaseVNode; }, +/* harmony export */ PS: function() { return /* binding */ hasInjectionContext; }, +/* harmony export */ Q3: function() { return /* binding */ createCommentVNode; }, +/* harmony export */ WQ: function() { return /* binding */ inject; }, +/* harmony export */ Wv: function() { return /* binding */ createBlock; }, +/* harmony export */ bo: function() { return /* binding */ withDirectives; }, +/* harmony export */ dY: function() { return /* binding */ nextTick; }, +/* harmony export */ qL: function() { return /* binding */ callWithAsyncErrorHandling; }, +/* harmony export */ uX: function() { return /* binding */ openBlock; }, +/* harmony export */ wB: function() { return /* binding */ watch; } +/* harmony export */ }); +/* unused harmony exports BaseTransition, BaseTransitionPropsValidators, Comment, DeprecationTypes, ErrorCodes, ErrorTypeStrings, KeepAlive, Static, Suspense, Teleport, Text, assertNumber, callWithErrorHandling, cloneVNode, compatUtils, createHydrationRenderer, createPropsRestProxy, createSlots, createTextVNode, defineAsyncComponent, defineComponent, defineEmits, defineExpose, defineModel, defineOptions, defineProps, defineSlots, devtools, getCurrentInstance, getTransitionRawChildren, guardReactiveProps, h, handleError, hydrateOnIdle, hydrateOnInteraction, hydrateOnMediaQuery, hydrateOnVisible, initCustomFormatter, isMemoSame, isRuntimeOnly, isVNode, mergeDefaults, mergeModels, mergeProps, onActivated, onBeforeMount, onBeforeUpdate, onDeactivated, onErrorCaptured, onRenderTracked, onRenderTriggered, onServerPrefetch, onUnmounted, onUpdated, popScopeId, provide, pushScopeId, queuePostFlushCb, registerRuntimeCompiler, renderList, renderSlot, resolveComponent, resolveDirective, resolveDynamicComponent, resolveFilter, resolveTransitionHooks, setBlockTracking, setDevtoolsHook, setTransitionHooks, ssrContextKey, ssrUtils, toHandlers, transformVNodeArgs, useAttrs, useId, useModel, useSSRContext, useSlots, useTemplateRef, useTransitionState, version, warn, watchEffect, watchPostEffect, watchSyncEffect, withAsyncContext, withCtx, withDefaults, withMemo, withScopeId */ +/* unused harmony import specifier */ var pauseTracking; +/* unused harmony import specifier */ var resetTracking; +/* unused harmony import specifier */ var isRef; +/* unused harmony import specifier */ var toRaw; +/* unused harmony import specifier */ var shallowRef; +/* unused harmony import specifier */ var isReactive; +/* unused harmony import specifier */ var ref; +/* unused harmony import specifier */ var isShallow; +/* unused harmony import specifier */ var shallowReadArray; +/* unused harmony import specifier */ var toReactive; +/* unused harmony import specifier */ var customRef; +/* unused harmony import specifier */ var track; +/* unused harmony import specifier */ var isString; +/* unused harmony import specifier */ var isFunction; +/* unused harmony import specifier */ var extend; +/* unused harmony import specifier */ var isBuiltInDirective; +/* unused harmony import specifier */ var isArray; +/* unused harmony import specifier */ var EMPTY_OBJ; +/* unused harmony import specifier */ var isOn; +/* unused harmony import specifier */ var isReservedProp; +/* unused harmony import specifier */ var normalizeClass; +/* unused harmony import specifier */ var stringifyStyle; +/* unused harmony import specifier */ var normalizeStyle; +/* unused harmony import specifier */ var isKnownSvgAttr; +/* unused harmony import specifier */ var isBooleanAttr; +/* unused harmony import specifier */ var isKnownHtmlAttr; +/* unused harmony import specifier */ var includeBooleanAttr; +/* unused harmony import specifier */ var isRenderableAttrValue; +/* unused harmony import specifier */ var getEscapedCssVarName; +/* unused harmony import specifier */ var invokeArrayFns; +/* unused harmony import specifier */ var isRegExp; +/* unused harmony import specifier */ var camelize; +/* unused harmony import specifier */ var capitalize; +/* unused harmony import specifier */ var isObject; +/* unused harmony import specifier */ var isSymbol; +/* unused harmony import specifier */ var toHandlerKey; +/* unused harmony import specifier */ var isGloballyAllowed; +/* unused harmony import specifier */ var NOOP; +/* unused harmony import specifier */ var isPromise; +/* unused harmony import specifier */ var makeMap; +/* unused harmony import specifier */ var toRawType; +/* unused harmony import specifier */ var hyphenate; +/* unused harmony import specifier */ var hasChanged; +/* unused harmony import specifier */ var toNumber; +/* harmony import */ var core_js_modules_es_array_includes_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4423); +/* harmony import */ var core_js_modules_es_array_includes_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_array_includes_js__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4114); +/* harmony import */ var core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var core_js_modules_es_iterator_constructor_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(8111); +/* harmony import */ var core_js_modules_es_iterator_constructor_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_constructor_js__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var core_js_modules_es_iterator_every_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(1148); +/* harmony import */ var core_js_modules_es_iterator_every_js__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_every_js__WEBPACK_IMPORTED_MODULE_3__); +/* harmony import */ var core_js_modules_es_iterator_filter_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(2489); +/* harmony import */ var core_js_modules_es_iterator_filter_js__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_filter_js__WEBPACK_IMPORTED_MODULE_4__); +/* harmony import */ var core_js_modules_es_iterator_for_each_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(7588); +/* harmony import */ var core_js_modules_es_iterator_for_each_js__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_for_each_js__WEBPACK_IMPORTED_MODULE_5__); +/* harmony import */ var core_js_modules_es_iterator_map_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(1701); +/* harmony import */ var core_js_modules_es_iterator_map_js__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_map_js__WEBPACK_IMPORTED_MODULE_6__); +/* harmony import */ var core_js_modules_es_iterator_reduce_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(8237); +/* harmony import */ var core_js_modules_es_iterator_reduce_js__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_reduce_js__WEBPACK_IMPORTED_MODULE_7__); +/* harmony import */ var core_js_modules_es_iterator_some_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(3579); +/* harmony import */ var core_js_modules_es_iterator_some_js__WEBPACK_IMPORTED_MODULE_8___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_iterator_some_js__WEBPACK_IMPORTED_MODULE_8__); +/* harmony import */ var core_js_modules_es_map_get_or_insert_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(5367); +/* harmony import */ var core_js_modules_es_map_get_or_insert_js__WEBPACK_IMPORTED_MODULE_9___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_map_get_or_insert_js__WEBPACK_IMPORTED_MODULE_9__); +/* harmony import */ var core_js_modules_es_map_get_or_insert_computed_js__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(2731); +/* harmony import */ var core_js_modules_es_map_get_or_insert_computed_js__WEBPACK_IMPORTED_MODULE_10___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_map_get_or_insert_computed_js__WEBPACK_IMPORTED_MODULE_10__); +/* harmony import */ var core_js_modules_es_set_difference_v2_js__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(7642); +/* harmony import */ var core_js_modules_es_set_difference_v2_js__WEBPACK_IMPORTED_MODULE_11___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_set_difference_v2_js__WEBPACK_IMPORTED_MODULE_11__); +/* harmony import */ var core_js_modules_es_set_intersection_v2_js__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(8004); +/* harmony import */ var core_js_modules_es_set_intersection_v2_js__WEBPACK_IMPORTED_MODULE_12___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_set_intersection_v2_js__WEBPACK_IMPORTED_MODULE_12__); +/* harmony import */ var core_js_modules_es_set_is_disjoint_from_v2_js__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(3853); +/* harmony import */ var core_js_modules_es_set_is_disjoint_from_v2_js__WEBPACK_IMPORTED_MODULE_13___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_set_is_disjoint_from_v2_js__WEBPACK_IMPORTED_MODULE_13__); +/* harmony import */ var core_js_modules_es_set_is_subset_of_v2_js__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(5876); +/* harmony import */ var core_js_modules_es_set_is_subset_of_v2_js__WEBPACK_IMPORTED_MODULE_14___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_set_is_subset_of_v2_js__WEBPACK_IMPORTED_MODULE_14__); +/* harmony import */ var core_js_modules_es_set_is_superset_of_v2_js__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(2475); +/* harmony import */ var core_js_modules_es_set_is_superset_of_v2_js__WEBPACK_IMPORTED_MODULE_15___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_set_is_superset_of_v2_js__WEBPACK_IMPORTED_MODULE_15__); +/* harmony import */ var core_js_modules_es_set_symmetric_difference_v2_js__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(5024); +/* harmony import */ var core_js_modules_es_set_symmetric_difference_v2_js__WEBPACK_IMPORTED_MODULE_16___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_set_symmetric_difference_v2_js__WEBPACK_IMPORTED_MODULE_16__); +/* harmony import */ var core_js_modules_es_set_union_v2_js__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(1698); +/* harmony import */ var core_js_modules_es_set_union_v2_js__WEBPACK_IMPORTED_MODULE_17___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_set_union_v2_js__WEBPACK_IMPORTED_MODULE_17__); +/* harmony import */ var core_js_modules_es_weak_map_get_or_insert_js__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(8454); +/* harmony import */ var core_js_modules_es_weak_map_get_or_insert_js__WEBPACK_IMPORTED_MODULE_18___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_weak_map_get_or_insert_js__WEBPACK_IMPORTED_MODULE_18__); +/* harmony import */ var core_js_modules_es_weak_map_get_or_insert_computed_js__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(9452); +/* harmony import */ var core_js_modules_es_weak_map_get_or_insert_computed_js__WEBPACK_IMPORTED_MODULE_19___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_weak_map_get_or_insert_computed_js__WEBPACK_IMPORTED_MODULE_19__); +/* harmony import */ var _vue_reactivity__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(144); +/* harmony import */ var _vue_shared__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(5187); + + + + + + + + + + + + + + + + + + + + +/** +* @vue/runtime-core v3.5.13 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/ + + + + +const stack = (/* unused pure expression or super */ null && ([])); +function pushWarningContext(vnode) { + stack.push(vnode); +} +function popWarningContext() { + stack.pop(); +} +let isWarning = false; +function warn$1(msg, ...args) { + if (isWarning) return; + isWarning = true; + pauseTracking(); + const instance = stack.length ? stack[stack.length - 1].component : null; + const appWarnHandler = instance && instance.appContext.config.warnHandler; + const trace = getComponentTrace(); + if (appWarnHandler) { + callWithErrorHandling(appWarnHandler, instance, 11, [ + // eslint-disable-next-line no-restricted-syntax + msg + args.map(a => { + var _a, _b; + return (_b = (_a = a.toString) == null ? void 0 : _a.call(a)) != null ? _b : JSON.stringify(a); + }).join(""), instance && instance.proxy, trace.map(({ + vnode + }) => `at <${formatComponentName(instance, vnode.type)}>`).join("\n"), trace]); + } else { + const warnArgs = [`[Vue warn]: ${msg}`, ...args]; + if (trace.length && + // avoid spamming console during tests + true) { + warnArgs.push(` +`, ...formatTrace(trace)); + } + console.warn(...warnArgs); + } + resetTracking(); + isWarning = false; +} +function getComponentTrace() { + let currentVNode = stack[stack.length - 1]; + if (!currentVNode) { + return []; + } + const normalizedStack = []; + while (currentVNode) { + const last = normalizedStack[0]; + if (last && last.vnode === currentVNode) { + last.recurseCount++; + } else { + normalizedStack.push({ + vnode: currentVNode, + recurseCount: 0 + }); + } + const parentInstance = currentVNode.component && currentVNode.component.parent; + currentVNode = parentInstance && parentInstance.vnode; + } + return normalizedStack; +} +function formatTrace(trace) { + const logs = []; + trace.forEach((entry, i) => { + logs.push(...(i === 0 ? [] : [` +`]), ...formatTraceEntry(entry)); + }); + return logs; +} +function formatTraceEntry({ + vnode, + recurseCount +}) { + const postfix = recurseCount > 0 ? `... (${recurseCount} recursive calls)` : ``; + const isRoot = vnode.component ? vnode.component.parent == null : false; + const open = ` at <${formatComponentName(vnode.component, vnode.type, isRoot)}`; + const close = `>` + postfix; + return vnode.props ? [open, ...formatProps(vnode.props), close] : [open + close]; +} +function formatProps(props) { + const res = []; + const keys = Object.keys(props); + keys.slice(0, 3).forEach(key => { + res.push(...formatProp(key, props[key])); + }); + if (keys.length > 3) { + res.push(` ...`); + } + return res; +} +function formatProp(key, value, raw) { + if (isString(value)) { + value = JSON.stringify(value); + return raw ? value : [`${key}=${value}`]; + } else if (typeof value === "number" || typeof value === "boolean" || value == null) { + return raw ? value : [`${key}=${value}`]; + } else if (isRef(value)) { + value = formatProp(key, toRaw(value.value), true); + return raw ? value : [`${key}=Ref<`, value, `>`]; + } else if (isFunction(value)) { + return [`${key}=fn${value.name ? `<${value.name}>` : ``}`]; + } else { + value = toRaw(value); + return raw ? value : [`${key}=`, value]; + } +} +function assertNumber(val, type) { + if (true) return; + // removed by dead control flow + +} +const ErrorCodes = (/* unused pure expression or super */ null && ({ + "SETUP_FUNCTION": 0, + "0": "SETUP_FUNCTION", + "RENDER_FUNCTION": 1, + "1": "RENDER_FUNCTION", + "NATIVE_EVENT_HANDLER": 5, + "5": "NATIVE_EVENT_HANDLER", + "COMPONENT_EVENT_HANDLER": 6, + "6": "COMPONENT_EVENT_HANDLER", + "VNODE_HOOK": 7, + "7": "VNODE_HOOK", + "DIRECTIVE_HOOK": 8, + "8": "DIRECTIVE_HOOK", + "TRANSITION_HOOK": 9, + "9": "TRANSITION_HOOK", + "APP_ERROR_HANDLER": 10, + "10": "APP_ERROR_HANDLER", + "APP_WARN_HANDLER": 11, + "11": "APP_WARN_HANDLER", + "FUNCTION_REF": 12, + "12": "FUNCTION_REF", + "ASYNC_COMPONENT_LOADER": 13, + "13": "ASYNC_COMPONENT_LOADER", + "SCHEDULER": 14, + "14": "SCHEDULER", + "COMPONENT_UPDATE": 15, + "15": "COMPONENT_UPDATE", + "APP_UNMOUNT_CLEANUP": 16, + "16": "APP_UNMOUNT_CLEANUP" +})); +const ErrorTypeStrings$1 = (/* unused pure expression or super */ null && ({ + ["sp"]: "serverPrefetch hook", + ["bc"]: "beforeCreate hook", + ["c"]: "created hook", + ["bm"]: "beforeMount hook", + ["m"]: "mounted hook", + ["bu"]: "beforeUpdate hook", + ["u"]: "updated", + ["bum"]: "beforeUnmount hook", + ["um"]: "unmounted hook", + ["a"]: "activated hook", + ["da"]: "deactivated hook", + ["ec"]: "errorCaptured hook", + ["rtc"]: "renderTracked hook", + ["rtg"]: "renderTriggered hook", + [0]: "setup function", + [1]: "render function", + [2]: "watcher getter", + [3]: "watcher callback", + [4]: "watcher cleanup function", + [5]: "native event handler", + [6]: "component event handler", + [7]: "vnode hook", + [8]: "directive hook", + [9]: "transition hook", + [10]: "app errorHandler", + [11]: "app warnHandler", + [12]: "ref function", + [13]: "async component loader", + [14]: "scheduler flush", + [15]: "component update", + [16]: "app unmount cleanup function" +})); +function callWithErrorHandling(fn, instance, type, args) { + try { + return args ? fn(...args) : fn(); + } catch (err) { + handleError(err, instance, type); + } +} +function callWithAsyncErrorHandling(fn, instance, type, args) { + if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .isFunction */ .Tn)(fn)) { + const res = callWithErrorHandling(fn, instance, type, args); + if (res && (0,_vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .isPromise */ .yL)(res)) { + res.catch(err => { + handleError(err, instance, type); + }); + } + return res; + } + if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .isArray */ .cy)(fn)) { + const values = []; + for (let i = 0; i < fn.length; i++) { + values.push(callWithAsyncErrorHandling(fn[i], instance, type, args)); + } + return values; + } else if (false) // removed by dead control flow +{} +} +function handleError(err, instance, type, throwInDev = true) { + const contextVNode = instance ? instance.vnode : null; + const { + errorHandler, + throwUnhandledErrorInProduction + } = instance && instance.appContext.config || _vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .EMPTY_OBJ */ .MZ; + if (instance) { + let cur = instance.parent; + const exposedInstance = instance.proxy; + const errorInfo = false ? 0 : `https://vuejs.org/error-reference/#runtime-${type}`; + while (cur) { + const errorCapturedHooks = cur.ec; + if (errorCapturedHooks) { + for (let i = 0; i < errorCapturedHooks.length; i++) { + if (errorCapturedHooks[i](err, exposedInstance, errorInfo) === false) { + return; + } + } + } + cur = cur.parent; + } + if (errorHandler) { + (0,_vue_reactivity__WEBPACK_IMPORTED_MODULE_20__/* .pauseTracking */ .C4)(); + callWithErrorHandling(errorHandler, null, 10, [err, exposedInstance, errorInfo]); + (0,_vue_reactivity__WEBPACK_IMPORTED_MODULE_20__/* .resetTracking */ .bl)(); + return; + } + } + logError(err, type, contextVNode, throwInDev, throwUnhandledErrorInProduction); +} +function logError(err, type, contextVNode, throwInDev = true, throwInProd = false) { + if (false) // removed by dead control flow +{} else if (throwInProd) { + throw err; + } else { + console.error(err); + } +} +const queue = []; +let flushIndex = -1; +const pendingPostFlushCbs = []; +let activePostFlushCbs = null; +let postFlushIndex = 0; +const resolvedPromise = /* @__PURE__ */Promise.resolve(); +let currentFlushPromise = null; +const RECURSION_LIMIT = 100; +function nextTick(fn) { + const p = currentFlushPromise || resolvedPromise; + return fn ? p.then(this ? fn.bind(this) : fn) : p; +} +function findInsertionIndex(id) { + let start = flushIndex + 1; + let end = queue.length; + while (start < end) { + const middle = start + end >>> 1; + const middleJob = queue[middle]; + const middleJobId = getId(middleJob); + if (middleJobId < id || middleJobId === id && middleJob.flags & 2) { + start = middle + 1; + } else { + end = middle; + } + } + return start; +} +function queueJob(job) { + if (!(job.flags & 1)) { + const jobId = getId(job); + const lastJob = queue[queue.length - 1]; + if (!lastJob || + // fast path when the job id is larger than the tail + !(job.flags & 2) && jobId >= getId(lastJob)) { + queue.push(job); + } else { + queue.splice(findInsertionIndex(jobId), 0, job); + } + job.flags |= 1; + queueFlush(); + } +} +function queueFlush() { + if (!currentFlushPromise) { + currentFlushPromise = resolvedPromise.then(flushJobs); + } +} +function queuePostFlushCb(cb) { + if (!(0,_vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .isArray */ .cy)(cb)) { + if (activePostFlushCbs && cb.id === -1) { + activePostFlushCbs.splice(postFlushIndex + 1, 0, cb); + } else if (!(cb.flags & 1)) { + pendingPostFlushCbs.push(cb); + cb.flags |= 1; + } + } else { + pendingPostFlushCbs.push(...cb); + } + queueFlush(); +} +function flushPreFlushCbs(instance, seen, i = flushIndex + 1) { + if (false) // removed by dead control flow +{} + for (; i < queue.length; i++) { + const cb = queue[i]; + if (cb && cb.flags & 2) { + if (instance && cb.id !== instance.uid) { + continue; + } + if (false) // removed by dead control flow +{} + queue.splice(i, 1); + i--; + if (cb.flags & 4) { + cb.flags &= ~1; + } + cb(); + if (!(cb.flags & 4)) { + cb.flags &= ~1; + } + } + } +} +function flushPostFlushCbs(seen) { + if (pendingPostFlushCbs.length) { + const deduped = [...new Set(pendingPostFlushCbs)].sort((a, b) => getId(a) - getId(b)); + pendingPostFlushCbs.length = 0; + if (activePostFlushCbs) { + activePostFlushCbs.push(...deduped); + return; + } + activePostFlushCbs = deduped; + if (false) // removed by dead control flow +{} + for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) { + const cb = activePostFlushCbs[postFlushIndex]; + if (false) // removed by dead control flow +{} + if (cb.flags & 4) { + cb.flags &= ~1; + } + if (!(cb.flags & 8)) cb(); + cb.flags &= ~1; + } + activePostFlushCbs = null; + postFlushIndex = 0; + } +} +const getId = job => job.id == null ? job.flags & 2 ? -1 : Infinity : job.id; +function flushJobs(seen) { + if (false) // removed by dead control flow +{} + const check = false ? 0 : _vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .NOOP */ .tE; + try { + for (flushIndex = 0; flushIndex < queue.length; flushIndex++) { + const job = queue[flushIndex]; + if (job && !(job.flags & 8)) { + if (false) // removed by dead control flow +{} + if (job.flags & 4) { + job.flags &= ~1; + } + callWithErrorHandling(job, job.i, job.i ? 15 : 14); + if (!(job.flags & 4)) { + job.flags &= ~1; + } + } + } + } finally { + for (; flushIndex < queue.length; flushIndex++) { + const job = queue[flushIndex]; + if (job) { + job.flags &= ~1; + } + } + flushIndex = -1; + queue.length = 0; + flushPostFlushCbs(seen); + currentFlushPromise = null; + if (queue.length || pendingPostFlushCbs.length) { + flushJobs(seen); + } + } +} +function checkRecursiveUpdates(seen, fn) { + const count = seen.get(fn) || 0; + if (count > RECURSION_LIMIT) { + const instance = fn.i; + const componentName = instance && getComponentName(instance.type); + handleError(`Maximum recursive updates exceeded${componentName ? ` in component <${componentName}>` : ``}. This means you have a reactive effect that is mutating its own dependencies and thus recursively triggering itself. Possible sources include component template, render function, updated hook or watcher source function.`, null, 10); + return true; + } + seen.set(fn, count + 1); + return false; +} +let isHmrUpdating = false; +const hmrDirtyComponents = /* @__PURE__ */(/* unused pure expression or super */ null && (new Map())); +if (false) // removed by dead control flow +{} +const map = /* @__PURE__ */(/* unused pure expression or super */ null && (new Map())); +function registerHMR(instance) { + const id = instance.type.__hmrId; + let record = map.get(id); + if (!record) { + createRecord(id, instance.type); + record = map.get(id); + } + record.instances.add(instance); +} +function unregisterHMR(instance) { + map.get(instance.type.__hmrId).instances.delete(instance); +} +function createRecord(id, initialDef) { + if (map.has(id)) { + return false; + } + map.set(id, { + initialDef: normalizeClassComponent(initialDef), + instances: /* @__PURE__ */new Set() + }); + return true; +} +function normalizeClassComponent(component) { + return isClassComponent(component) ? component.__vccOpts : component; +} +function rerender(id, newRender) { + const record = map.get(id); + if (!record) { + return; + } + record.initialDef.render = newRender; + [...record.instances].forEach(instance => { + if (newRender) { + instance.render = newRender; + normalizeClassComponent(instance.type).render = newRender; + } + instance.renderCache = []; + isHmrUpdating = true; + instance.update(); + isHmrUpdating = false; + }); +} +function reload(id, newComp) { + const record = map.get(id); + if (!record) return; + newComp = normalizeClassComponent(newComp); + updateComponentDef(record.initialDef, newComp); + const instances = [...record.instances]; + for (let i = 0; i < instances.length; i++) { + const instance = instances[i]; + const oldComp = normalizeClassComponent(instance.type); + let dirtyInstances = hmrDirtyComponents.get(oldComp); + if (!dirtyInstances) { + if (oldComp !== record.initialDef) { + updateComponentDef(oldComp, newComp); + } + hmrDirtyComponents.set(oldComp, dirtyInstances = /* @__PURE__ */new Set()); + } + dirtyInstances.add(instance); + instance.appContext.propsCache.delete(instance.type); + instance.appContext.emitsCache.delete(instance.type); + instance.appContext.optionsCache.delete(instance.type); + if (instance.ceReload) { + dirtyInstances.add(instance); + instance.ceReload(newComp.styles); + dirtyInstances.delete(instance); + } else if (instance.parent) { + queueJob(() => { + isHmrUpdating = true; + instance.parent.update(); + isHmrUpdating = false; + dirtyInstances.delete(instance); + }); + } else if (instance.appContext.reload) { + instance.appContext.reload(); + } else if (typeof window !== "undefined") { + window.location.reload(); + } else { + console.warn("[HMR] Root or manually mounted instance modified. Full reload required."); + } + if (instance.root.ce && instance !== instance.root) { + instance.root.ce._removeChildStyle(oldComp); + } + } + queuePostFlushCb(() => { + hmrDirtyComponents.clear(); + }); +} +function updateComponentDef(oldComp, newComp) { + extend(oldComp, newComp); + for (const key in oldComp) { + if (key !== "__file" && !(key in newComp)) { + delete oldComp[key]; + } + } +} +function tryWrap(fn) { + return (id, arg) => { + try { + return fn(id, arg); + } catch (e) { + console.error(e); + console.warn(`[HMR] Something went wrong during Vue component hot-reload. Full reload required.`); + } + }; +} +let devtools$1; +let buffer = (/* unused pure expression or super */ null && ([])); +let devtoolsNotInstalled = false; +function emit$1(event, ...args) { + if (devtools$1) { + devtools$1.emit(event, ...args); + } else if (!devtoolsNotInstalled) { + buffer.push({ + event, + args + }); + } +} +function setDevtoolsHook$1(hook, target) { + var _a, _b; + devtools$1 = hook; + if (devtools$1) { + devtools$1.enabled = true; + buffer.forEach(({ + event, + args + }) => devtools$1.emit(event, ...args)); + buffer = []; + } else if ( + // handle late devtools injection - only do this if we are in an actual + // browser environment to avoid the timer handle stalling test runner exit + // (#4815) + typeof window !== "undefined" && + // some envs mock window but not fully + window.HTMLElement && + // also exclude jsdom + // eslint-disable-next-line no-restricted-syntax + !((_b = (_a = window.navigator) == null ? void 0 : _a.userAgent) == null ? void 0 : _b.includes("jsdom"))) { + const replay = target.__VUE_DEVTOOLS_HOOK_REPLAY__ = target.__VUE_DEVTOOLS_HOOK_REPLAY__ || []; + replay.push(newHook => { + setDevtoolsHook$1(newHook, target); + }); + setTimeout(() => { + if (!devtools$1) { + target.__VUE_DEVTOOLS_HOOK_REPLAY__ = null; + devtoolsNotInstalled = true; + buffer = []; + } + }, 3e3); + } else { + devtoolsNotInstalled = true; + buffer = []; + } +} +function devtoolsInitApp(app, version) { + emit$1("app:init" /* APP_INIT */, app, version, { + Fragment, + Text, + Comment, + Static + }); +} +function devtoolsUnmountApp(app) { + emit$1("app:unmount" /* APP_UNMOUNT */, app); +} +const devtoolsComponentAdded = /* @__PURE__ */(/* unused pure expression or super */ null && (createDevtoolsComponentHook("component:added" /* COMPONENT_ADDED */))); +const devtoolsComponentUpdated = /* @__PURE__ */(/* unused pure expression or super */ null && (createDevtoolsComponentHook("component:updated" /* COMPONENT_UPDATED */))); +const _devtoolsComponentRemoved = /* @__PURE__ */(/* unused pure expression or super */ null && (createDevtoolsComponentHook("component:removed" /* COMPONENT_REMOVED */))); +const devtoolsComponentRemoved = component => { + if (devtools$1 && typeof devtools$1.cleanupBuffer === "function" && + // remove the component if it wasn't buffered + !devtools$1.cleanupBuffer(component)) { + _devtoolsComponentRemoved(component); + } +}; +/*! #__NO_SIDE_EFFECTS__ */ +// @__NO_SIDE_EFFECTS__ +function createDevtoolsComponentHook(hook) { + return component => { + emit$1(hook, component.appContext.app, component.uid, component.parent ? component.parent.uid : void 0, component); + }; +} +const devtoolsPerfStart = /* @__PURE__ */(/* unused pure expression or super */ null && (createDevtoolsPerformanceHook("perf:start" /* PERFORMANCE_START */))); +const devtoolsPerfEnd = /* @__PURE__ */(/* unused pure expression or super */ null && (createDevtoolsPerformanceHook("perf:end" /* PERFORMANCE_END */))); +function createDevtoolsPerformanceHook(hook) { + return (component, type, time) => { + emit$1(hook, component.appContext.app, component.uid, component, type, time); + }; +} +function devtoolsComponentEmit(component, event, params) { + emit$1("component:emit" /* COMPONENT_EMIT */, component.appContext.app, component, event, params); +} +let currentRenderingInstance = null; +let currentScopeId = null; +function setCurrentRenderingInstance(instance) { + const prev = currentRenderingInstance; + currentRenderingInstance = instance; + currentScopeId = instance && instance.type.__scopeId || null; + return prev; +} +function pushScopeId(id) { + currentScopeId = id; +} +function popScopeId() { + currentScopeId = null; +} +const withScopeId = _id => withCtx; +function withCtx(fn, ctx = currentRenderingInstance, isNonScopedSlot) { + if (!ctx) return fn; + if (fn._n) { + return fn; + } + const renderFnWithContext = (...args) => { + if (renderFnWithContext._d) { + setBlockTracking(-1); + } + const prevInstance = setCurrentRenderingInstance(ctx); + let res; + try { + res = fn(...args); + } finally { + setCurrentRenderingInstance(prevInstance); + if (renderFnWithContext._d) { + setBlockTracking(1); + } + } + if (false) // removed by dead control flow +{} + return res; + }; + renderFnWithContext._n = true; + renderFnWithContext._c = true; + renderFnWithContext._d = true; + return renderFnWithContext; +} +function validateDirectiveName(name) { + if (isBuiltInDirective(name)) { + warn$1("Do not use built-in directive ids as custom directive id: " + name); + } +} +function withDirectives(vnode, directives) { + if (currentRenderingInstance === null) { + false && 0; + return vnode; + } + const instance = getComponentPublicInstance(currentRenderingInstance); + const bindings = vnode.dirs || (vnode.dirs = []); + for (let i = 0; i < directives.length; i++) { + let [dir, value, arg, modifiers = _vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .EMPTY_OBJ */ .MZ] = directives[i]; + if (dir) { + if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .isFunction */ .Tn)(dir)) { + dir = { + mounted: dir, + updated: dir + }; + } + if (dir.deep) { + (0,_vue_reactivity__WEBPACK_IMPORTED_MODULE_20__/* .traverse */ .hV)(value); + } + bindings.push({ + dir, + instance, + value, + oldValue: void 0, + arg, + modifiers + }); + } + } + return vnode; +} +function invokeDirectiveHook(vnode, prevVNode, instance, name) { + const bindings = vnode.dirs; + const oldBindings = prevVNode && prevVNode.dirs; + for (let i = 0; i < bindings.length; i++) { + const binding = bindings[i]; + if (oldBindings) { + binding.oldValue = oldBindings[i].value; + } + let hook = binding.dir[name]; + if (hook) { + (0,_vue_reactivity__WEBPACK_IMPORTED_MODULE_20__/* .pauseTracking */ .C4)(); + callWithAsyncErrorHandling(hook, instance, 8, [vnode.el, binding, vnode, prevVNode]); + (0,_vue_reactivity__WEBPACK_IMPORTED_MODULE_20__/* .resetTracking */ .bl)(); + } + } +} +const TeleportEndKey = Symbol("_vte"); +const isTeleport = type => type.__isTeleport; +const isTeleportDisabled = props => props && (props.disabled || props.disabled === ""); +const isTeleportDeferred = props => props && (props.defer || props.defer === ""); +const isTargetSVG = target => typeof SVGElement !== "undefined" && target instanceof SVGElement; +const isTargetMathML = target => typeof MathMLElement === "function" && target instanceof MathMLElement; +const resolveTarget = (props, select) => { + const targetSelector = props && props.to; + if (isString(targetSelector)) { + if (!select) { + false && 0; + return null; + } else { + const target = select(targetSelector); + if (false) // removed by dead control flow +{} + return target; + } + } else { + if (false) // removed by dead control flow +{} + return targetSelector; + } +}; +const TeleportImpl = (/* unused pure expression or super */ null && ({ + name: "Teleport", + __isTeleport: true, + process(n1, n2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized, internals) { + const { + mc: mountChildren, + pc: patchChildren, + pbc: patchBlockChildren, + o: { + insert, + querySelector, + createText, + createComment + } + } = internals; + const disabled = isTeleportDisabled(n2.props); + let { + shapeFlag, + children, + dynamicChildren + } = n2; + if (false) // removed by dead control flow +{} + if (n1 == null) { + const placeholder = n2.el = false ? 0 : createText(""); + const mainAnchor = n2.anchor = false ? 0 : createText(""); + insert(placeholder, container, anchor); + insert(mainAnchor, container, anchor); + const mount = (container2, anchor2) => { + if (shapeFlag & 16) { + if (parentComponent && parentComponent.isCE) { + parentComponent.ce._teleportTarget = container2; + } + mountChildren(children, container2, anchor2, parentComponent, parentSuspense, namespace, slotScopeIds, optimized); + } + }; + const mountToTarget = () => { + const target = n2.target = resolveTarget(n2.props, querySelector); + const targetAnchor = prepareAnchor(target, n2, createText, insert); + if (target) { + if (namespace !== "svg" && isTargetSVG(target)) { + namespace = "svg"; + } else if (namespace !== "mathml" && isTargetMathML(target)) { + namespace = "mathml"; + } + if (!disabled) { + mount(target, targetAnchor); + updateCssVars(n2, false); + } + } else if (false) // removed by dead control flow +{} + }; + if (disabled) { + mount(container, mainAnchor); + updateCssVars(n2, true); + } + if (isTeleportDeferred(n2.props)) { + queuePostRenderEffect(() => { + mountToTarget(); + n2.el.__isMounted = true; + }, parentSuspense); + } else { + mountToTarget(); + } + } else { + if (isTeleportDeferred(n2.props) && !n1.el.__isMounted) { + queuePostRenderEffect(() => { + TeleportImpl.process(n1, n2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized, internals); + delete n1.el.__isMounted; + }, parentSuspense); + return; + } + n2.el = n1.el; + n2.targetStart = n1.targetStart; + const mainAnchor = n2.anchor = n1.anchor; + const target = n2.target = n1.target; + const targetAnchor = n2.targetAnchor = n1.targetAnchor; + const wasDisabled = isTeleportDisabled(n1.props); + const currentContainer = wasDisabled ? container : target; + const currentAnchor = wasDisabled ? mainAnchor : targetAnchor; + if (namespace === "svg" || isTargetSVG(target)) { + namespace = "svg"; + } else if (namespace === "mathml" || isTargetMathML(target)) { + namespace = "mathml"; + } + if (dynamicChildren) { + patchBlockChildren(n1.dynamicChildren, dynamicChildren, currentContainer, parentComponent, parentSuspense, namespace, slotScopeIds); + traverseStaticChildren(n1, n2, true); + } else if (!optimized) { + patchChildren(n1, n2, currentContainer, currentAnchor, parentComponent, parentSuspense, namespace, slotScopeIds, false); + } + if (disabled) { + if (!wasDisabled) { + moveTeleport(n2, container, mainAnchor, internals, 1); + } else { + if (n2.props && n1.props && n2.props.to !== n1.props.to) { + n2.props.to = n1.props.to; + } + } + } else { + if ((n2.props && n2.props.to) !== (n1.props && n1.props.to)) { + const nextTarget = n2.target = resolveTarget(n2.props, querySelector); + if (nextTarget) { + moveTeleport(n2, nextTarget, null, internals, 0); + } else if (false) // removed by dead control flow +{} + } else if (wasDisabled) { + moveTeleport(n2, target, targetAnchor, internals, 1); + } + } + updateCssVars(n2, disabled); + } + }, + remove(vnode, parentComponent, parentSuspense, { + um: unmount, + o: { + remove: hostRemove + } + }, doRemove) { + const { + shapeFlag, + children, + anchor, + targetStart, + targetAnchor, + target, + props + } = vnode; + if (target) { + hostRemove(targetStart); + hostRemove(targetAnchor); + } + doRemove && hostRemove(anchor); + if (shapeFlag & 16) { + const shouldRemove = doRemove || !isTeleportDisabled(props); + for (let i = 0; i < children.length; i++) { + const child = children[i]; + unmount(child, parentComponent, parentSuspense, shouldRemove, !!child.dynamicChildren); + } + } + }, + move: moveTeleport, + hydrate: hydrateTeleport +})); +function moveTeleport(vnode, container, parentAnchor, { + o: { + insert + }, + m: move +}, moveType = 2) { + if (moveType === 0) { + insert(vnode.targetAnchor, container, parentAnchor); + } + const { + el, + anchor, + shapeFlag, + children, + props + } = vnode; + const isReorder = moveType === 2; + if (isReorder) { + insert(el, container, parentAnchor); + } + if (!isReorder || isTeleportDisabled(props)) { + if (shapeFlag & 16) { + for (let i = 0; i < children.length; i++) { + move(children[i], container, parentAnchor, 2); + } + } + } + if (isReorder) { + insert(anchor, container, parentAnchor); + } +} +function hydrateTeleport(node, vnode, parentComponent, parentSuspense, slotScopeIds, optimized, { + o: { + nextSibling, + parentNode, + querySelector, + insert, + createText + } +}, hydrateChildren) { + const target = vnode.target = resolveTarget(vnode.props, querySelector); + if (target) { + const disabled = isTeleportDisabled(vnode.props); + const targetNode = target._lpa || target.firstChild; + if (vnode.shapeFlag & 16) { + if (disabled) { + vnode.anchor = hydrateChildren(nextSibling(node), vnode, parentNode(node), parentComponent, parentSuspense, slotScopeIds, optimized); + vnode.targetStart = targetNode; + vnode.targetAnchor = targetNode && nextSibling(targetNode); + } else { + vnode.anchor = nextSibling(node); + let targetAnchor = targetNode; + while (targetAnchor) { + if (targetAnchor && targetAnchor.nodeType === 8) { + if (targetAnchor.data === "teleport start anchor") { + vnode.targetStart = targetAnchor; + } else if (targetAnchor.data === "teleport anchor") { + vnode.targetAnchor = targetAnchor; + target._lpa = vnode.targetAnchor && nextSibling(vnode.targetAnchor); + break; + } + } + targetAnchor = nextSibling(targetAnchor); + } + if (!vnode.targetAnchor) { + prepareAnchor(target, vnode, createText, insert); + } + hydrateChildren(targetNode && nextSibling(targetNode), vnode, target, parentComponent, parentSuspense, slotScopeIds, optimized); + } + } + updateCssVars(vnode, disabled); + } + return vnode.anchor && nextSibling(vnode.anchor); +} +const Teleport = (/* unused pure expression or super */ null && (TeleportImpl)); +function updateCssVars(vnode, isDisabled) { + const ctx = vnode.ctx; + if (ctx && ctx.ut) { + let node, anchor; + if (isDisabled) { + node = vnode.el; + anchor = vnode.anchor; + } else { + node = vnode.targetStart; + anchor = vnode.targetAnchor; + } + while (node && node !== anchor) { + if (node.nodeType === 1) node.setAttribute("data-v-owner", ctx.uid); + node = node.nextSibling; + } + ctx.ut(); + } +} +function prepareAnchor(target, vnode, createText, insert) { + const targetStart = vnode.targetStart = createText(""); + const targetAnchor = vnode.targetAnchor = createText(""); + targetStart[TeleportEndKey] = targetAnchor; + if (target) { + insert(targetStart, target); + insert(targetAnchor, target); + } + return targetAnchor; +} +const leaveCbKey = Symbol("_leaveCb"); +const enterCbKey = Symbol("_enterCb"); +function useTransitionState() { + const state = { + isMounted: false, + isLeaving: false, + isUnmounting: false, + leavingVNodes: /* @__PURE__ */new Map() + }; + onMounted(() => { + state.isMounted = true; + }); + onBeforeUnmount(() => { + state.isUnmounting = true; + }); + return state; +} +const TransitionHookValidator = (/* unused pure expression or super */ null && ([Function, Array])); +const BaseTransitionPropsValidators = (/* unused pure expression or super */ null && ({ + mode: String, + appear: Boolean, + persisted: Boolean, + // enter + onBeforeEnter: TransitionHookValidator, + onEnter: TransitionHookValidator, + onAfterEnter: TransitionHookValidator, + onEnterCancelled: TransitionHookValidator, + // leave + onBeforeLeave: TransitionHookValidator, + onLeave: TransitionHookValidator, + onAfterLeave: TransitionHookValidator, + onLeaveCancelled: TransitionHookValidator, + // appear + onBeforeAppear: TransitionHookValidator, + onAppear: TransitionHookValidator, + onAfterAppear: TransitionHookValidator, + onAppearCancelled: TransitionHookValidator +})); +const recursiveGetSubtree = instance => { + const subTree = instance.subTree; + return subTree.component ? recursiveGetSubtree(subTree.component) : subTree; +}; +const BaseTransitionImpl = (/* unused pure expression or super */ null && ({ + name: `BaseTransition`, + props: BaseTransitionPropsValidators, + setup(props, { + slots + }) { + const instance = getCurrentInstance(); + const state = useTransitionState(); + return () => { + const children = slots.default && getTransitionRawChildren(slots.default(), true); + if (!children || !children.length) { + return; + } + const child = findNonCommentChild(children); + const rawProps = toRaw(props); + const { + mode + } = rawProps; + if (false) // removed by dead control flow +{} + if (state.isLeaving) { + return emptyPlaceholder(child); + } + const innerChild = getInnerChild$1(child); + if (!innerChild) { + return emptyPlaceholder(child); + } + let enterHooks = resolveTransitionHooks(innerChild, rawProps, state, instance, + // #11061, ensure enterHooks is fresh after clone + hooks => enterHooks = hooks); + if (innerChild.type !== Comment) { + setTransitionHooks(innerChild, enterHooks); + } + let oldInnerChild = instance.subTree && getInnerChild$1(instance.subTree); + if (oldInnerChild && oldInnerChild.type !== Comment && !isSameVNodeType(innerChild, oldInnerChild) && recursiveGetSubtree(instance).type !== Comment) { + let leavingHooks = resolveTransitionHooks(oldInnerChild, rawProps, state, instance); + setTransitionHooks(oldInnerChild, leavingHooks); + if (mode === "out-in" && innerChild.type !== Comment) { + state.isLeaving = true; + leavingHooks.afterLeave = () => { + state.isLeaving = false; + if (!(instance.job.flags & 8)) { + instance.update(); + } + delete leavingHooks.afterLeave; + oldInnerChild = void 0; + }; + return emptyPlaceholder(child); + } else if (mode === "in-out" && innerChild.type !== Comment) { + leavingHooks.delayLeave = (el, earlyRemove, delayedLeave) => { + const leavingVNodesCache = getLeavingNodesForType(state, oldInnerChild); + leavingVNodesCache[String(oldInnerChild.key)] = oldInnerChild; + el[leaveCbKey] = () => { + earlyRemove(); + el[leaveCbKey] = void 0; + delete enterHooks.delayedLeave; + oldInnerChild = void 0; + }; + enterHooks.delayedLeave = () => { + delayedLeave(); + delete enterHooks.delayedLeave; + oldInnerChild = void 0; + }; + }; + } else { + oldInnerChild = void 0; + } + } else if (oldInnerChild) { + oldInnerChild = void 0; + } + return child; + }; + } +})); +function findNonCommentChild(children) { + let child = children[0]; + if (children.length > 1) { + let hasFound = false; + for (const c of children) { + if (c.type !== Comment) { + if (false) // removed by dead control flow +{} + child = c; + hasFound = true; + if (true) break; + } + } + } + return child; +} +const BaseTransition = (/* unused pure expression or super */ null && (BaseTransitionImpl)); +function getLeavingNodesForType(state, vnode) { + const { + leavingVNodes + } = state; + let leavingVNodesCache = leavingVNodes.get(vnode.type); + if (!leavingVNodesCache) { + leavingVNodesCache = /* @__PURE__ */Object.create(null); + leavingVNodes.set(vnode.type, leavingVNodesCache); + } + return leavingVNodesCache; +} +function resolveTransitionHooks(vnode, props, state, instance, postClone) { + const { + appear, + mode, + persisted = false, + onBeforeEnter, + onEnter, + onAfterEnter, + onEnterCancelled, + onBeforeLeave, + onLeave, + onAfterLeave, + onLeaveCancelled, + onBeforeAppear, + onAppear, + onAfterAppear, + onAppearCancelled + } = props; + const key = String(vnode.key); + const leavingVNodesCache = getLeavingNodesForType(state, vnode); + const callHook = (hook, args) => { + hook && callWithAsyncErrorHandling(hook, instance, 9, args); + }; + const callAsyncHook = (hook, args) => { + const done = args[1]; + callHook(hook, args); + if (isArray(hook)) { + if (hook.every(hook2 => hook2.length <= 1)) done(); + } else if (hook.length <= 1) { + done(); + } + }; + const hooks = { + mode, + persisted, + beforeEnter(el) { + let hook = onBeforeEnter; + if (!state.isMounted) { + if (appear) { + hook = onBeforeAppear || onBeforeEnter; + } else { + return; + } + } + if (el[leaveCbKey]) { + el[leaveCbKey](true + /* cancelled */); + } + const leavingVNode = leavingVNodesCache[key]; + if (leavingVNode && isSameVNodeType(vnode, leavingVNode) && leavingVNode.el[leaveCbKey]) { + leavingVNode.el[leaveCbKey](); + } + callHook(hook, [el]); + }, + enter(el) { + let hook = onEnter; + let afterHook = onAfterEnter; + let cancelHook = onEnterCancelled; + if (!state.isMounted) { + if (appear) { + hook = onAppear || onEnter; + afterHook = onAfterAppear || onAfterEnter; + cancelHook = onAppearCancelled || onEnterCancelled; + } else { + return; + } + } + let called = false; + const done = el[enterCbKey] = cancelled => { + if (called) return; + called = true; + if (cancelled) { + callHook(cancelHook, [el]); + } else { + callHook(afterHook, [el]); + } + if (hooks.delayedLeave) { + hooks.delayedLeave(); + } + el[enterCbKey] = void 0; + }; + if (hook) { + callAsyncHook(hook, [el, done]); + } else { + done(); + } + }, + leave(el, remove) { + const key2 = String(vnode.key); + if (el[enterCbKey]) { + el[enterCbKey](true + /* cancelled */); + } + if (state.isUnmounting) { + return remove(); + } + callHook(onBeforeLeave, [el]); + let called = false; + const done = el[leaveCbKey] = cancelled => { + if (called) return; + called = true; + remove(); + if (cancelled) { + callHook(onLeaveCancelled, [el]); + } else { + callHook(onAfterLeave, [el]); + } + el[leaveCbKey] = void 0; + if (leavingVNodesCache[key2] === vnode) { + delete leavingVNodesCache[key2]; + } + }; + leavingVNodesCache[key2] = vnode; + if (onLeave) { + callAsyncHook(onLeave, [el, done]); + } else { + done(); + } + }, + clone(vnode2) { + const hooks2 = resolveTransitionHooks(vnode2, props, state, instance, postClone); + if (postClone) postClone(hooks2); + return hooks2; + } + }; + return hooks; +} +function emptyPlaceholder(vnode) { + if (isKeepAlive(vnode)) { + vnode = cloneVNode(vnode); + vnode.children = null; + return vnode; + } +} +function getInnerChild$1(vnode) { + if (!isKeepAlive(vnode)) { + if (isTeleport(vnode.type) && vnode.children) { + return findNonCommentChild(vnode.children); + } + return vnode; + } + if (false) // removed by dead control flow +{} + const { + shapeFlag, + children + } = vnode; + if (children) { + if (shapeFlag & 16) { + return children[0]; + } + if (shapeFlag & 32 && isFunction(children.default)) { + return children.default(); + } + } +} +function setTransitionHooks(vnode, hooks) { + if (vnode.shapeFlag & 6 && vnode.component) { + vnode.transition = hooks; + setTransitionHooks(vnode.component.subTree, hooks); + } else if (vnode.shapeFlag & 128) { + vnode.ssContent.transition = hooks.clone(vnode.ssContent); + vnode.ssFallback.transition = hooks.clone(vnode.ssFallback); + } else { + vnode.transition = hooks; + } +} +function getTransitionRawChildren(children, keepComment = false, parentKey) { + let ret = []; + let keyedFragmentCount = 0; + for (let i = 0; i < children.length; i++) { + let child = children[i]; + const key = parentKey == null ? child.key : String(parentKey) + String(child.key != null ? child.key : i); + if (child.type === Fragment) { + if (child.patchFlag & 128) keyedFragmentCount++; + ret = ret.concat(getTransitionRawChildren(child.children, keepComment, key)); + } else if (keepComment || child.type !== Comment) { + ret.push(key != null ? cloneVNode(child, { + key + }) : child); + } + } + if (keyedFragmentCount > 1) { + for (let i = 0; i < ret.length; i++) { + ret[i].patchFlag = -2; + } + } + return ret; +} + +/*! #__NO_SIDE_EFFECTS__ */ +// @__NO_SIDE_EFFECTS__ +function defineComponent(options, extraOptions) { + return isFunction(options) ? + // #8236: extend call and options.name access are considered side-effects + // by Rollup, so we have to wrap it in a pure-annotated IIFE. + /* @__PURE__ */ + (() => extend({ + name: options.name + }, extraOptions, { + setup: options + }))() : options; +} +function useId() { + const i = getCurrentInstance(); + if (i) { + return (i.appContext.config.idPrefix || "v") + "-" + i.ids[0] + i.ids[1]++; + } else if (false) // removed by dead control flow +{} + return ""; +} +function markAsyncBoundary(instance) { + instance.ids = [instance.ids[0] + instance.ids[2]++ + "-", 0, 0]; +} +const knownTemplateRefs = /* @__PURE__ */(/* unused pure expression or super */ null && (new WeakSet())); +function useTemplateRef(key) { + const i = getCurrentInstance(); + const r = shallowRef(null); + if (i) { + const refs = i.refs === EMPTY_OBJ ? i.refs = {} : i.refs; + let desc; + if (false) // removed by dead control flow +{} else { + Object.defineProperty(refs, key, { + enumerable: true, + get: () => r.value, + set: val => r.value = val + }); + } + } else if (false) // removed by dead control flow +{} + const ret = false ? 0 : r; + if (false) // removed by dead control flow +{} + return ret; +} +function setRef(rawRef, oldRawRef, parentSuspense, vnode, isUnmount = false) { + if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .isArray */ .cy)(rawRef)) { + rawRef.forEach((r, i) => setRef(r, oldRawRef && ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .isArray */ .cy)(oldRawRef) ? oldRawRef[i] : oldRawRef), parentSuspense, vnode, isUnmount)); + return; + } + if (isAsyncWrapper(vnode) && !isUnmount) { + if (vnode.shapeFlag & 512 && vnode.type.__asyncResolved && vnode.component.subTree.component) { + setRef(rawRef, oldRawRef, parentSuspense, vnode.component.subTree); + } + return; + } + const refValue = vnode.shapeFlag & 4 ? getComponentPublicInstance(vnode.component) : vnode.el; + const value = isUnmount ? null : refValue; + const { + i: owner, + r: ref + } = rawRef; + if (false) // removed by dead control flow +{} + const oldRef = oldRawRef && oldRawRef.r; + const refs = owner.refs === _vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .EMPTY_OBJ */ .MZ ? owner.refs = {} : owner.refs; + const setupState = owner.setupState; + const rawSetupState = (0,_vue_reactivity__WEBPACK_IMPORTED_MODULE_20__/* .toRaw */ .ux)(setupState); + const canSetSetupRef = setupState === _vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .EMPTY_OBJ */ .MZ ? () => false : key => { + if (false) // removed by dead control flow +{} + return (0,_vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .hasOwn */ .$3)(rawSetupState, key); + }; + if (oldRef != null && oldRef !== ref) { + if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .isString */ .Kg)(oldRef)) { + refs[oldRef] = null; + if (canSetSetupRef(oldRef)) { + setupState[oldRef] = null; + } + } else if ((0,_vue_reactivity__WEBPACK_IMPORTED_MODULE_20__/* .isRef */ .i9)(oldRef)) { + oldRef.value = null; + } + } + if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .isFunction */ .Tn)(ref)) { + callWithErrorHandling(ref, owner, 12, [value, refs]); + } else { + const _isString = (0,_vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .isString */ .Kg)(ref); + const _isRef = (0,_vue_reactivity__WEBPACK_IMPORTED_MODULE_20__/* .isRef */ .i9)(ref); + if (_isString || _isRef) { + const doSet = () => { + if (rawRef.f) { + const existing = _isString ? canSetSetupRef(ref) ? setupState[ref] : refs[ref] : ref.value; + if (isUnmount) { + (0,_vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .isArray */ .cy)(existing) && (0,_vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .remove */ .TF)(existing, refValue); + } else { + if (!(0,_vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .isArray */ .cy)(existing)) { + if (_isString) { + refs[ref] = [refValue]; + if (canSetSetupRef(ref)) { + setupState[ref] = refs[ref]; + } + } else { + ref.value = [refValue]; + if (rawRef.k) refs[rawRef.k] = ref.value; + } + } else if (!existing.includes(refValue)) { + existing.push(refValue); + } + } + } else if (_isString) { + refs[ref] = value; + if (canSetSetupRef(ref)) { + setupState[ref] = value; + } + } else if (_isRef) { + ref.value = value; + if (rawRef.k) refs[rawRef.k] = value; + } else if (false) // removed by dead control flow +{} + }; + if (value) { + doSet.id = -1; + queuePostRenderEffect(doSet, parentSuspense); + } else { + doSet(); + } + } else if (false) // removed by dead control flow +{} + } +} +let hasLoggedMismatchError = false; +const logMismatchError = () => { + if (hasLoggedMismatchError) { + return; + } + console.error("Hydration completed but contains mismatches."); + hasLoggedMismatchError = true; +}; +const isSVGContainer = container => container.namespaceURI.includes("svg") && container.tagName !== "foreignObject"; +const isMathMLContainer = container => container.namespaceURI.includes("MathML"); +const getContainerType = container => { + if (container.nodeType !== 1) return void 0; + if (isSVGContainer(container)) return "svg"; + if (isMathMLContainer(container)) return "mathml"; + return void 0; +}; +const isComment = node => node.nodeType === 8; +function createHydrationFunctions(rendererInternals) { + const { + mt: mountComponent, + p: patch, + o: { + patchProp, + createText, + nextSibling, + parentNode, + remove, + insert, + createComment + } + } = rendererInternals; + const hydrate = (vnode, container) => { + if (!container.hasChildNodes()) { + ( false) && 0; + patch(null, vnode, container); + flushPostFlushCbs(); + container._vnode = vnode; + return; + } + hydrateNode(container.firstChild, vnode, null, null, null); + flushPostFlushCbs(); + container._vnode = vnode; + }; + const hydrateNode = (node, vnode, parentComponent, parentSuspense, slotScopeIds, optimized = false) => { + optimized = optimized || !!vnode.dynamicChildren; + const isFragmentStart = isComment(node) && node.data === "["; + const onMismatch = () => handleMismatch(node, vnode, parentComponent, parentSuspense, slotScopeIds, isFragmentStart); + const { + type, + ref, + shapeFlag, + patchFlag + } = vnode; + let domType = node.nodeType; + vnode.el = node; + if (false) // removed by dead control flow +{} + if (patchFlag === -2) { + optimized = false; + vnode.dynamicChildren = null; + } + let nextNode = null; + switch (type) { + case Text: + if (domType !== 3) { + if (vnode.children === "") { + insert(vnode.el = createText(""), parentNode(node), node); + nextNode = node; + } else { + nextNode = onMismatch(); + } + } else { + if (node.data !== vnode.children) { + ( false) && 0; + logMismatchError(); + node.data = vnode.children; + } + nextNode = nextSibling(node); + } + break; + case Comment: + if (isTemplateNode(node)) { + nextNode = nextSibling(node); + replaceNode(vnode.el = node.content.firstChild, node, parentComponent); + } else if (domType !== 8 || isFragmentStart) { + nextNode = onMismatch(); + } else { + nextNode = nextSibling(node); + } + break; + case Static: + if (isFragmentStart) { + node = nextSibling(node); + domType = node.nodeType; + } + if (domType === 1 || domType === 3) { + nextNode = node; + const needToAdoptContent = !vnode.children.length; + for (let i = 0; i < vnode.staticCount; i++) { + if (needToAdoptContent) vnode.children += nextNode.nodeType === 1 ? nextNode.outerHTML : nextNode.data; + if (i === vnode.staticCount - 1) { + vnode.anchor = nextNode; + } + nextNode = nextSibling(nextNode); + } + return isFragmentStart ? nextSibling(nextNode) : nextNode; + } else { + onMismatch(); + } + break; + case Fragment: + if (!isFragmentStart) { + nextNode = onMismatch(); + } else { + nextNode = hydrateFragment(node, vnode, parentComponent, parentSuspense, slotScopeIds, optimized); + } + break; + default: + if (shapeFlag & 1) { + if ((domType !== 1 || vnode.type.toLowerCase() !== node.tagName.toLowerCase()) && !isTemplateNode(node)) { + nextNode = onMismatch(); + } else { + nextNode = hydrateElement(node, vnode, parentComponent, parentSuspense, slotScopeIds, optimized); + } + } else if (shapeFlag & 6) { + vnode.slotScopeIds = slotScopeIds; + const container = parentNode(node); + if (isFragmentStart) { + nextNode = locateClosingAnchor(node); + } else if (isComment(node) && node.data === "teleport start") { + nextNode = locateClosingAnchor(node, node.data, "teleport end"); + } else { + nextNode = nextSibling(node); + } + mountComponent(vnode, container, null, parentComponent, parentSuspense, getContainerType(container), optimized); + if (isAsyncWrapper(vnode) && !vnode.type.__asyncResolved) { + let subTree; + if (isFragmentStart) { + subTree = createVNode(Fragment); + subTree.anchor = nextNode ? nextNode.previousSibling : container.lastChild; + } else { + subTree = node.nodeType === 3 ? createTextVNode("") : createVNode("div"); + } + subTree.el = node; + vnode.component.subTree = subTree; + } + } else if (shapeFlag & 64) { + if (domType !== 8) { + nextNode = onMismatch(); + } else { + nextNode = vnode.type.hydrate(node, vnode, parentComponent, parentSuspense, slotScopeIds, optimized, rendererInternals, hydrateChildren); + } + } else if (shapeFlag & 128) { + nextNode = vnode.type.hydrate(node, vnode, parentComponent, parentSuspense, getContainerType(parentNode(node)), slotScopeIds, optimized, rendererInternals, hydrateNode); + } else if (false) // removed by dead control flow +{} + } + if (ref != null) { + setRef(ref, null, parentSuspense, vnode); + } + return nextNode; + }; + const hydrateElement = (el, vnode, parentComponent, parentSuspense, slotScopeIds, optimized) => { + optimized = optimized || !!vnode.dynamicChildren; + const { + type, + props, + patchFlag, + shapeFlag, + dirs, + transition + } = vnode; + const forcePatch = type === "input" || type === "option"; + if ( false || forcePatch || patchFlag !== -1) { + if (dirs) { + invokeDirectiveHook(vnode, null, parentComponent, "created"); + } + let needCallTransitionHooks = false; + if (isTemplateNode(el)) { + needCallTransitionHooks = needTransition(null, + // no need check parentSuspense in hydration + transition) && parentComponent && parentComponent.vnode.props && parentComponent.vnode.props.appear; + const content = el.content.firstChild; + if (needCallTransitionHooks) { + transition.beforeEnter(content); + } + replaceNode(content, el, parentComponent); + vnode.el = el = content; + } + if (shapeFlag & 16 && + // skip if element has innerHTML / textContent + !(props && (props.innerHTML || props.textContent))) { + let next = hydrateChildren(el.firstChild, vnode, el, parentComponent, parentSuspense, slotScopeIds, optimized); + let hasWarned = false; + while (next) { + if (!isMismatchAllowed(el, 1 /* CHILDREN */)) { + if (false) // removed by dead control flow +{} + logMismatchError(); + } + const cur = next; + next = next.nextSibling; + remove(cur); + } + } else if (shapeFlag & 8) { + let clientText = vnode.children; + if (clientText[0] === "\n" && (el.tagName === "PRE" || el.tagName === "TEXTAREA")) { + clientText = clientText.slice(1); + } + if (el.textContent !== clientText) { + if (!isMismatchAllowed(el, 0 /* TEXT */)) { + ( false) && 0; + logMismatchError(); + } + el.textContent = vnode.children; + } + } + if (props) { + if ( false || forcePatch || !optimized || patchFlag & (16 | 32)) { + const isCustomElement = el.tagName.includes("-"); + for (const key in props) { + if (false) // removed by dead control flow +{} + if (forcePatch && (key.endsWith("value") || key === "indeterminate") || isOn(key) && !isReservedProp(key) || + // force hydrate v-bind with .prop modifiers + key[0] === "." || isCustomElement) { + patchProp(el, key, null, props[key], void 0, parentComponent); + } + } + } else if (props.onClick) { + patchProp(el, "onClick", null, props.onClick, void 0, parentComponent); + } else if (patchFlag & 4 && isReactive(props.style)) { + for (const key in props.style) props.style[key]; + } + } + let vnodeHooks; + if (vnodeHooks = props && props.onVnodeBeforeMount) { + invokeVNodeHook(vnodeHooks, parentComponent, vnode); + } + if (dirs) { + invokeDirectiveHook(vnode, null, parentComponent, "beforeMount"); + } + if ((vnodeHooks = props && props.onVnodeMounted) || dirs || needCallTransitionHooks) { + queueEffectWithSuspense(() => { + vnodeHooks && invokeVNodeHook(vnodeHooks, parentComponent, vnode); + needCallTransitionHooks && transition.enter(el); + dirs && invokeDirectiveHook(vnode, null, parentComponent, "mounted"); + }, parentSuspense); + } + } + return el.nextSibling; + }; + const hydrateChildren = (node, parentVNode, container, parentComponent, parentSuspense, slotScopeIds, optimized) => { + optimized = optimized || !!parentVNode.dynamicChildren; + const children = parentVNode.children; + const l = children.length; + let hasWarned = false; + for (let i = 0; i < l; i++) { + const vnode = optimized ? children[i] : children[i] = normalizeVNode(children[i]); + const isText = vnode.type === Text; + if (node) { + if (isText && !optimized) { + if (i + 1 < l && normalizeVNode(children[i + 1]).type === Text) { + insert(createText(node.data.slice(vnode.children.length)), container, nextSibling(node)); + node.data = vnode.children; + } + } + node = hydrateNode(node, vnode, parentComponent, parentSuspense, slotScopeIds, optimized); + } else if (isText && !vnode.children) { + insert(vnode.el = createText(""), container); + } else { + if (!isMismatchAllowed(container, 1 /* CHILDREN */)) { + if (false) // removed by dead control flow +{} + logMismatchError(); + } + patch(null, vnode, container, null, parentComponent, parentSuspense, getContainerType(container), slotScopeIds); + } + } + return node; + }; + const hydrateFragment = (node, vnode, parentComponent, parentSuspense, slotScopeIds, optimized) => { + const { + slotScopeIds: fragmentSlotScopeIds + } = vnode; + if (fragmentSlotScopeIds) { + slotScopeIds = slotScopeIds ? slotScopeIds.concat(fragmentSlotScopeIds) : fragmentSlotScopeIds; + } + const container = parentNode(node); + const next = hydrateChildren(nextSibling(node), vnode, container, parentComponent, parentSuspense, slotScopeIds, optimized); + if (next && isComment(next) && next.data === "]") { + return nextSibling(vnode.anchor = next); + } else { + logMismatchError(); + insert(vnode.anchor = createComment(`]`), container, next); + return next; + } + }; + const handleMismatch = (node, vnode, parentComponent, parentSuspense, slotScopeIds, isFragment) => { + if (!isMismatchAllowed(node.parentElement, 1 /* CHILDREN */)) { + ( false) && 0; + logMismatchError(); + } + vnode.el = null; + if (isFragment) { + const end = locateClosingAnchor(node); + while (true) { + const next2 = nextSibling(node); + if (next2 && next2 !== end) { + remove(next2); + } else { + break; + } + } + } + const next = nextSibling(node); + const container = parentNode(node); + remove(node); + patch(null, vnode, container, next, parentComponent, parentSuspense, getContainerType(container), slotScopeIds); + if (parentComponent) { + parentComponent.vnode.el = vnode.el; + updateHOCHostEl(parentComponent, vnode.el); + } + return next; + }; + const locateClosingAnchor = (node, open = "[", close = "]") => { + let match = 0; + while (node) { + node = nextSibling(node); + if (node && isComment(node)) { + if (node.data === open) match++; + if (node.data === close) { + if (match === 0) { + return nextSibling(node); + } else { + match--; + } + } + } + } + return node; + }; + const replaceNode = (newNode, oldNode, parentComponent) => { + const parentNode2 = oldNode.parentNode; + if (parentNode2) { + parentNode2.replaceChild(newNode, oldNode); + } + let parent = parentComponent; + while (parent) { + if (parent.vnode.el === oldNode) { + parent.vnode.el = parent.subTree.el = newNode; + } + parent = parent.parent; + } + }; + const isTemplateNode = node => { + return node.nodeType === 1 && node.tagName === "TEMPLATE"; + }; + return [hydrate, hydrateNode]; +} +function propHasMismatch(el, key, clientValue, vnode, instance) { + let mismatchType; + let mismatchKey; + let actual; + let expected; + if (key === "class") { + actual = el.getAttribute("class"); + expected = normalizeClass(clientValue); + if (!isSetEqual(toClassSet(actual || ""), toClassSet(expected))) { + mismatchType = 2 /* CLASS */; + mismatchKey = `class`; + } + } else if (key === "style") { + actual = el.getAttribute("style") || ""; + expected = isString(clientValue) ? clientValue : stringifyStyle(normalizeStyle(clientValue)); + const actualMap = toStyleMap(actual); + const expectedMap = toStyleMap(expected); + if (vnode.dirs) { + for (const { + dir, + value + } of vnode.dirs) { + if (dir.name === "show" && !value) { + expectedMap.set("display", "none"); + } + } + } + if (instance) { + resolveCssVars(instance, vnode, expectedMap); + } + if (!isMapEqual(actualMap, expectedMap)) { + mismatchType = 3 /* STYLE */; + mismatchKey = "style"; + } + } else if (el instanceof SVGElement && isKnownSvgAttr(key) || el instanceof HTMLElement && (isBooleanAttr(key) || isKnownHtmlAttr(key))) { + if (isBooleanAttr(key)) { + actual = el.hasAttribute(key); + expected = includeBooleanAttr(clientValue); + } else if (clientValue == null) { + actual = el.hasAttribute(key); + expected = false; + } else { + if (el.hasAttribute(key)) { + actual = el.getAttribute(key); + } else if (key === "value" && el.tagName === "TEXTAREA") { + actual = el.value; + } else { + actual = false; + } + expected = isRenderableAttrValue(clientValue) ? String(clientValue) : false; + } + if (actual !== expected) { + mismatchType = 4 /* ATTRIBUTE */; + mismatchKey = key; + } + } + if (mismatchType != null && !isMismatchAllowed(el, mismatchType)) { + const format = v => v === false ? `(not rendered)` : `${mismatchKey}="${v}"`; + const preSegment = `Hydration ${MismatchTypeString[mismatchType]} mismatch on`; + const postSegment = ` + - rendered on server: ${format(actual)} + - expected on client: ${format(expected)} + Note: this mismatch is check-only. The DOM will not be rectified in production due to performance overhead. + You should fix the source of the mismatch.`; + { + warn$1(preSegment, el, postSegment); + } + return true; + } + return false; +} +function toClassSet(str) { + return new Set(str.trim().split(/\s+/)); +} +function isSetEqual(a, b) { + if (a.size !== b.size) { + return false; + } + for (const s of a) { + if (!b.has(s)) { + return false; + } + } + return true; +} +function toStyleMap(str) { + const styleMap = /* @__PURE__ */new Map(); + for (const item of str.split(";")) { + let [key, value] = item.split(":"); + key = key.trim(); + value = value && value.trim(); + if (key && value) { + styleMap.set(key, value); + } + } + return styleMap; +} +function isMapEqual(a, b) { + if (a.size !== b.size) { + return false; + } + for (const [key, value] of a) { + if (value !== b.get(key)) { + return false; + } + } + return true; +} +function resolveCssVars(instance, vnode, expectedMap) { + const root = instance.subTree; + if (instance.getCssVars && (vnode === root || root && root.type === Fragment && root.children.includes(vnode))) { + const cssVars = instance.getCssVars(); + for (const key in cssVars) { + expectedMap.set(`--${getEscapedCssVarName(key, false)}`, String(cssVars[key])); + } + } + if (vnode === root && instance.parent) { + resolveCssVars(instance.parent, instance.vnode, expectedMap); + } +} +const allowMismatchAttr = "data-allow-mismatch"; +const MismatchTypeString = (/* unused pure expression or super */ null && ({ + [0 /* TEXT */]: "text", + [1 /* CHILDREN */]: "children", + [2 /* CLASS */]: "class", + [3 /* STYLE */]: "style", + [4 /* ATTRIBUTE */]: "attribute" +})); +function isMismatchAllowed(el, allowedType) { + if (allowedType === 0 /* TEXT */ || allowedType === 1 /* CHILDREN */) { + while (el && !el.hasAttribute(allowMismatchAttr)) { + el = el.parentElement; + } + } + const allowedAttr = el && el.getAttribute(allowMismatchAttr); + if (allowedAttr == null) { + return false; + } else if (allowedAttr === "") { + return true; + } else { + const list = allowedAttr.split(","); + if (allowedType === 0 /* TEXT */ && list.includes("children")) { + return true; + } + return allowedAttr.split(",").includes(MismatchTypeString[allowedType]); + } +} +const requestIdleCallback = (0,_vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .getGlobalThis */ .We)().requestIdleCallback || (cb => setTimeout(cb, 1)); +const cancelIdleCallback = (0,_vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .getGlobalThis */ .We)().cancelIdleCallback || (id => clearTimeout(id)); +const hydrateOnIdle = (timeout = 1e4) => hydrate => { + const id = requestIdleCallback(hydrate, { + timeout + }); + return () => cancelIdleCallback(id); +}; +function elementIsVisibleInViewport(el) { + const { + top, + left, + bottom, + right + } = el.getBoundingClientRect(); + const { + innerHeight, + innerWidth + } = window; + return (top > 0 && top < innerHeight || bottom > 0 && bottom < innerHeight) && (left > 0 && left < innerWidth || right > 0 && right < innerWidth); +} +const hydrateOnVisible = opts => (hydrate, forEach) => { + const ob = new IntersectionObserver(entries => { + for (const e of entries) { + if (!e.isIntersecting) continue; + ob.disconnect(); + hydrate(); + break; + } + }, opts); + forEach(el => { + if (!(el instanceof Element)) return; + if (elementIsVisibleInViewport(el)) { + hydrate(); + ob.disconnect(); + return false; + } + ob.observe(el); + }); + return () => ob.disconnect(); +}; +const hydrateOnMediaQuery = query => hydrate => { + if (query) { + const mql = matchMedia(query); + if (mql.matches) { + hydrate(); + } else { + mql.addEventListener("change", hydrate, { + once: true + }); + return () => mql.removeEventListener("change", hydrate); + } + } +}; +const hydrateOnInteraction = (interactions = []) => (hydrate, forEach) => { + if (isString(interactions)) interactions = [interactions]; + let hasHydrated = false; + const doHydrate = e => { + if (!hasHydrated) { + hasHydrated = true; + teardown(); + hydrate(); + e.target.dispatchEvent(new e.constructor(e.type, e)); + } + }; + const teardown = () => { + forEach(el => { + for (const i of interactions) { + el.removeEventListener(i, doHydrate); + } + }); + }; + forEach(el => { + for (const i of interactions) { + el.addEventListener(i, doHydrate, { + once: true + }); + } + }); + return teardown; +}; +function forEachElement(node, cb) { + if (isComment(node) && node.data === "[") { + let depth = 1; + let next = node.nextSibling; + while (next) { + if (next.nodeType === 1) { + const result = cb(next); + if (result === false) { + break; + } + } else if (isComment(next)) { + if (next.data === "]") { + if (--depth === 0) break; + } else if (next.data === "[") { + depth++; + } + } + next = next.nextSibling; + } + } else { + cb(node); + } +} +const isAsyncWrapper = i => !!i.type.__asyncLoader; +/*! #__NO_SIDE_EFFECTS__ */ +// @__NO_SIDE_EFFECTS__ +function defineAsyncComponent(source) { + if (isFunction(source)) { + source = { + loader: source + }; + } + const { + loader, + loadingComponent, + errorComponent, + delay = 200, + hydrate: hydrateStrategy, + timeout, + // undefined = never times out + suspensible = true, + onError: userOnError + } = source; + let pendingRequest = null; + let resolvedComp; + let retries = 0; + const retry = () => { + retries++; + pendingRequest = null; + return load(); + }; + const load = () => { + let thisRequest; + return pendingRequest || (thisRequest = pendingRequest = loader().catch(err => { + err = err instanceof Error ? err : new Error(String(err)); + if (userOnError) { + return new Promise((resolve, reject) => { + const userRetry = () => resolve(retry()); + const userFail = () => reject(err); + userOnError(err, userRetry, userFail, retries + 1); + }); + } else { + throw err; + } + }).then(comp => { + if (thisRequest !== pendingRequest && pendingRequest) { + return pendingRequest; + } + if (false) // removed by dead control flow +{} + if (comp && (comp.__esModule || comp[Symbol.toStringTag] === "Module")) { + comp = comp.default; + } + if (false) // removed by dead control flow +{} + resolvedComp = comp; + return comp; + })); + }; + return defineComponent({ + name: "AsyncComponentWrapper", + __asyncLoader: load, + __asyncHydrate(el, instance, hydrate) { + const doHydrate = hydrateStrategy ? () => { + const teardown = hydrateStrategy(hydrate, cb => forEachElement(el, cb)); + if (teardown) { + (instance.bum || (instance.bum = [])).push(teardown); + } + } : hydrate; + if (resolvedComp) { + doHydrate(); + } else { + load().then(() => !instance.isUnmounted && doHydrate()); + } + }, + get __asyncResolved() { + return resolvedComp; + }, + setup() { + const instance = currentInstance; + markAsyncBoundary(instance); + if (resolvedComp) { + return () => createInnerComp(resolvedComp, instance); + } + const onError = err => { + pendingRequest = null; + handleError(err, instance, 13, !errorComponent); + }; + if (suspensible && instance.suspense || isInSSRComponentSetup) { + return load().then(comp => { + return () => createInnerComp(comp, instance); + }).catch(err => { + onError(err); + return () => errorComponent ? createVNode(errorComponent, { + error: err + }) : null; + }); + } + const loaded = ref(false); + const error = ref(); + const delayed = ref(!!delay); + if (delay) { + setTimeout(() => { + delayed.value = false; + }, delay); + } + if (timeout != null) { + setTimeout(() => { + if (!loaded.value && !error.value) { + const err = new Error(`Async component timed out after ${timeout}ms.`); + onError(err); + error.value = err; + } + }, timeout); + } + load().then(() => { + loaded.value = true; + if (instance.parent && isKeepAlive(instance.parent.vnode)) { + instance.parent.update(); + } + }).catch(err => { + onError(err); + error.value = err; + }); + return () => { + if (loaded.value && resolvedComp) { + return createInnerComp(resolvedComp, instance); + } else if (error.value && errorComponent) { + return createVNode(errorComponent, { + error: error.value + }); + } else if (loadingComponent && !delayed.value) { + return createVNode(loadingComponent); + } + }; + } + }); +} +function createInnerComp(comp, parent) { + const { + ref: ref2, + props, + children, + ce + } = parent.vnode; + const vnode = createVNode(comp, props, children); + vnode.ref = ref2; + vnode.ce = ce; + delete parent.vnode.ce; + return vnode; +} +const isKeepAlive = vnode => vnode.type.__isKeepAlive; +const KeepAliveImpl = (/* unused pure expression or super */ null && ({ + name: `KeepAlive`, + // Marker for special handling inside the renderer. We are not using a === + // check directly on KeepAlive in the renderer, because importing it directly + // would prevent it from being tree-shaken. + __isKeepAlive: true, + props: { + include: [String, RegExp, Array], + exclude: [String, RegExp, Array], + max: [String, Number] + }, + setup(props, { + slots + }) { + const instance = getCurrentInstance(); + const sharedContext = instance.ctx; + if (!sharedContext.renderer) { + return () => { + const children = slots.default && slots.default(); + return children && children.length === 1 ? children[0] : children; + }; + } + const cache = /* @__PURE__ */new Map(); + const keys = /* @__PURE__ */new Set(); + let current = null; + if (false) // removed by dead control flow +{} + const parentSuspense = instance.suspense; + const { + renderer: { + p: patch, + m: move, + um: _unmount, + o: { + createElement + } + } + } = sharedContext; + const storageContainer = createElement("div"); + sharedContext.activate = (vnode, container, anchor, namespace, optimized) => { + const instance2 = vnode.component; + move(vnode, container, anchor, 0, parentSuspense); + patch(instance2.vnode, vnode, container, anchor, instance2, parentSuspense, namespace, vnode.slotScopeIds, optimized); + queuePostRenderEffect(() => { + instance2.isDeactivated = false; + if (instance2.a) { + invokeArrayFns(instance2.a); + } + const vnodeHook = vnode.props && vnode.props.onVnodeMounted; + if (vnodeHook) { + invokeVNodeHook(vnodeHook, instance2.parent, vnode); + } + }, parentSuspense); + if (false) // removed by dead control flow +{} + }; + sharedContext.deactivate = vnode => { + const instance2 = vnode.component; + invalidateMount(instance2.m); + invalidateMount(instance2.a); + move(vnode, storageContainer, null, 1, parentSuspense); + queuePostRenderEffect(() => { + if (instance2.da) { + invokeArrayFns(instance2.da); + } + const vnodeHook = vnode.props && vnode.props.onVnodeUnmounted; + if (vnodeHook) { + invokeVNodeHook(vnodeHook, instance2.parent, vnode); + } + instance2.isDeactivated = true; + }, parentSuspense); + if (false) // removed by dead control flow +{} + }; + function unmount(vnode) { + resetShapeFlag(vnode); + _unmount(vnode, instance, parentSuspense, true); + } + function pruneCache(filter) { + cache.forEach((vnode, key) => { + const name = getComponentName(vnode.type); + if (name && !filter(name)) { + pruneCacheEntry(key); + } + }); + } + function pruneCacheEntry(key) { + const cached = cache.get(key); + if (cached && (!current || !isSameVNodeType(cached, current))) { + unmount(cached); + } else if (current) { + resetShapeFlag(current); + } + cache.delete(key); + keys.delete(key); + } + watch(() => [props.include, props.exclude], ([include, exclude]) => { + include && pruneCache(name => matches(include, name)); + exclude && pruneCache(name => !matches(exclude, name)); + }, + // prune post-render after `current` has been updated + { + flush: "post", + deep: true + }); + let pendingCacheKey = null; + const cacheSubtree = () => { + if (pendingCacheKey != null) { + if (isSuspense(instance.subTree.type)) { + queuePostRenderEffect(() => { + cache.set(pendingCacheKey, getInnerChild(instance.subTree)); + }, instance.subTree.suspense); + } else { + cache.set(pendingCacheKey, getInnerChild(instance.subTree)); + } + } + }; + onMounted(cacheSubtree); + onUpdated(cacheSubtree); + onBeforeUnmount(() => { + cache.forEach(cached => { + const { + subTree, + suspense + } = instance; + const vnode = getInnerChild(subTree); + if (cached.type === vnode.type && cached.key === vnode.key) { + resetShapeFlag(vnode); + const da = vnode.component.da; + da && queuePostRenderEffect(da, suspense); + return; + } + unmount(cached); + }); + }); + return () => { + pendingCacheKey = null; + if (!slots.default) { + return current = null; + } + const children = slots.default(); + const rawVNode = children[0]; + if (children.length > 1) { + if (false) // removed by dead control flow +{} + current = null; + return children; + } else if (!isVNode(rawVNode) || !(rawVNode.shapeFlag & 4) && !(rawVNode.shapeFlag & 128)) { + current = null; + return rawVNode; + } + let vnode = getInnerChild(rawVNode); + if (vnode.type === Comment) { + current = null; + return vnode; + } + const comp = vnode.type; + const name = getComponentName(isAsyncWrapper(vnode) ? vnode.type.__asyncResolved || {} : comp); + const { + include, + exclude, + max + } = props; + if (include && (!name || !matches(include, name)) || exclude && name && matches(exclude, name)) { + vnode.shapeFlag &= ~256; + current = vnode; + return rawVNode; + } + const key = vnode.key == null ? comp : vnode.key; + const cachedVNode = cache.get(key); + if (vnode.el) { + vnode = cloneVNode(vnode); + if (rawVNode.shapeFlag & 128) { + rawVNode.ssContent = vnode; + } + } + pendingCacheKey = key; + if (cachedVNode) { + vnode.el = cachedVNode.el; + vnode.component = cachedVNode.component; + if (vnode.transition) { + setTransitionHooks(vnode, vnode.transition); + } + vnode.shapeFlag |= 512; + keys.delete(key); + keys.add(key); + } else { + keys.add(key); + if (max && keys.size > parseInt(max, 10)) { + pruneCacheEntry(keys.values().next().value); + } + } + vnode.shapeFlag |= 256; + current = vnode; + return isSuspense(rawVNode.type) ? rawVNode : vnode; + }; + } +})); +const KeepAlive = (/* unused pure expression or super */ null && (KeepAliveImpl)); +function matches(pattern, name) { + if (isArray(pattern)) { + return pattern.some(p => matches(p, name)); + } else if (isString(pattern)) { + return pattern.split(",").includes(name); + } else if (isRegExp(pattern)) { + pattern.lastIndex = 0; + return pattern.test(name); + } + return false; +} +function onActivated(hook, target) { + registerKeepAliveHook(hook, "a", target); +} +function onDeactivated(hook, target) { + registerKeepAliveHook(hook, "da", target); +} +function registerKeepAliveHook(hook, type, target = currentInstance) { + const wrappedHook = hook.__wdc || (hook.__wdc = () => { + let current = target; + while (current) { + if (current.isDeactivated) { + return; + } + current = current.parent; + } + return hook(); + }); + injectHook(type, wrappedHook, target); + if (target) { + let current = target.parent; + while (current && current.parent) { + if (isKeepAlive(current.parent.vnode)) { + injectToKeepAliveRoot(wrappedHook, type, target, current); + } + current = current.parent; + } + } +} +function injectToKeepAliveRoot(hook, type, target, keepAliveRoot) { + const injected = injectHook(type, hook, keepAliveRoot, true + /* prepend */); + onUnmounted(() => { + (0,_vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .remove */ .TF)(keepAliveRoot[type], injected); + }, target); +} +function resetShapeFlag(vnode) { + vnode.shapeFlag &= ~256; + vnode.shapeFlag &= ~512; +} +function getInnerChild(vnode) { + return vnode.shapeFlag & 128 ? vnode.ssContent : vnode; +} +function injectHook(type, hook, target = currentInstance, prepend = false) { + if (target) { + const hooks = target[type] || (target[type] = []); + const wrappedHook = hook.__weh || (hook.__weh = (...args) => { + (0,_vue_reactivity__WEBPACK_IMPORTED_MODULE_20__/* .pauseTracking */ .C4)(); + const reset = setCurrentInstance(target); + const res = callWithAsyncErrorHandling(hook, target, type, args); + reset(); + (0,_vue_reactivity__WEBPACK_IMPORTED_MODULE_20__/* .resetTracking */ .bl)(); + return res; + }); + if (prepend) { + hooks.unshift(wrappedHook); + } else { + hooks.push(wrappedHook); + } + return wrappedHook; + } else if (false) // removed by dead control flow +{} +} +const createHook = lifecycle => (hook, target = currentInstance) => { + if (!isInSSRComponentSetup || lifecycle === "sp") { + injectHook(lifecycle, (...args) => hook(...args), target); + } +}; +const onBeforeMount = createHook("bm"); +const onMounted = createHook("m"); +const onBeforeUpdate = createHook("bu"); +const onUpdated = createHook("u"); +const onBeforeUnmount = createHook("bum"); +const onUnmounted = createHook("um"); +const onServerPrefetch = createHook("sp"); +const onRenderTriggered = createHook("rtg"); +const onRenderTracked = createHook("rtc"); +function onErrorCaptured(hook, target = currentInstance) { + injectHook("ec", hook, target); +} +const COMPONENTS = "components"; +const DIRECTIVES = "directives"; +function resolveComponent(name, maybeSelfReference) { + return resolveAsset(COMPONENTS, name, true, maybeSelfReference) || name; +} +const NULL_DYNAMIC_COMPONENT = Symbol.for("v-ndc"); +function resolveDynamicComponent(component) { + if (isString(component)) { + return resolveAsset(COMPONENTS, component, false) || component; + } else { + return component || NULL_DYNAMIC_COMPONENT; + } +} +function resolveDirective(name) { + return resolveAsset(DIRECTIVES, name); +} +function resolveAsset(type, name, warnMissing = true, maybeSelfReference = false) { + const instance = currentRenderingInstance || currentInstance; + if (instance) { + const Component = instance.type; + if (type === COMPONENTS) { + const selfName = getComponentName(Component, false); + if (selfName && (selfName === name || selfName === camelize(name) || selfName === capitalize(camelize(name)))) { + return Component; + } + } + const res = + // local registration + // check instance[type] first which is resolved for options API + resolve(instance[type] || Component[type], name) || + // global registration + resolve(instance.appContext[type], name); + if (!res && maybeSelfReference) { + return Component; + } + if (false) // removed by dead control flow +{} + return res; + } else if (false) // removed by dead control flow +{} +} +function resolve(registry, name) { + return registry && (registry[name] || registry[camelize(name)] || registry[capitalize(camelize(name))]); +} +function renderList(source, renderItem, cache, index) { + let ret; + const cached = cache && cache[index]; + const sourceIsArray = isArray(source); + if (sourceIsArray || isString(source)) { + const sourceIsReactiveArray = sourceIsArray && isReactive(source); + let needsWrap = false; + if (sourceIsReactiveArray) { + needsWrap = !isShallow(source); + source = shallowReadArray(source); + } + ret = new Array(source.length); + for (let i = 0, l = source.length; i < l; i++) { + ret[i] = renderItem(needsWrap ? toReactive(source[i]) : source[i], i, void 0, cached && cached[i]); + } + } else if (typeof source === "number") { + if (false) // removed by dead control flow +{} + ret = new Array(source); + for (let i = 0; i < source; i++) { + ret[i] = renderItem(i + 1, i, void 0, cached && cached[i]); + } + } else if (isObject(source)) { + if (source[Symbol.iterator]) { + ret = Array.from(source, (item, i) => renderItem(item, i, void 0, cached && cached[i])); + } else { + const keys = Object.keys(source); + ret = new Array(keys.length); + for (let i = 0, l = keys.length; i < l; i++) { + const key = keys[i]; + ret[i] = renderItem(source[key], key, i, cached && cached[i]); + } + } + } else { + ret = []; + } + if (cache) { + cache[index] = ret; + } + return ret; +} +function createSlots(slots, dynamicSlots) { + for (let i = 0; i < dynamicSlots.length; i++) { + const slot = dynamicSlots[i]; + if (isArray(slot)) { + for (let j = 0; j < slot.length; j++) { + slots[slot[j].name] = slot[j].fn; + } + } else if (slot) { + slots[slot.name] = slot.key ? (...args) => { + const res = slot.fn(...args); + if (res) res.key = slot.key; + return res; + } : slot.fn; + } + } + return slots; +} +function renderSlot(slots, name, props = {}, fallback, noSlotted) { + if (currentRenderingInstance.ce || currentRenderingInstance.parent && isAsyncWrapper(currentRenderingInstance.parent) && currentRenderingInstance.parent.ce) { + if (name !== "default") props.name = name; + return openBlock(), createBlock(Fragment, null, [createVNode("slot", props, fallback && fallback())], 64); + } + let slot = slots[name]; + if (false) // removed by dead control flow +{} + if (slot && slot._c) { + slot._d = false; + } + openBlock(); + const validSlotContent = slot && ensureValidVNode(slot(props)); + const slotKey = props.key || + // slot content array of a dynamic conditional slot may have a branch + // key attached in the `createSlots` helper, respect that + validSlotContent && validSlotContent.key; + const rendered = createBlock(Fragment, { + key: (slotKey && !isSymbol(slotKey) ? slotKey : `_${name}`) + ( + // #7256 force differentiate fallback content from actual content + !validSlotContent && fallback ? "_fb" : "") + }, validSlotContent || (fallback ? fallback() : []), validSlotContent && slots._ === 1 ? 64 : -2); + if (!noSlotted && rendered.scopeId) { + rendered.slotScopeIds = [rendered.scopeId + "-s"]; + } + if (slot && slot._c) { + slot._d = true; + } + return rendered; +} +function ensureValidVNode(vnodes) { + return vnodes.some(child => { + if (!isVNode(child)) return true; + if (child.type === Comment) return false; + if (child.type === Fragment && !ensureValidVNode(child.children)) return false; + return true; + }) ? vnodes : null; +} +function toHandlers(obj, preserveCaseIfNecessary) { + const ret = {}; + if (false) // removed by dead control flow +{} + for (const key in obj) { + ret[preserveCaseIfNecessary && /[A-Z]/.test(key) ? `on:${key}` : toHandlerKey(key)] = obj[key]; + } + return ret; +} +const getPublicInstance = i => { + if (!i) return null; + if (isStatefulComponent(i)) return getComponentPublicInstance(i); + return getPublicInstance(i.parent); +}; +const publicPropertiesMap = +// Move PURE marker to new line to workaround compiler discarding it +// due to type annotation +/* @__PURE__ */ +(0,_vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .extend */ .X$)(/* @__PURE__ */Object.create(null), { + $: i => i, + $el: i => i.vnode.el, + $data: i => i.data, + $props: i => false ? 0 : i.props, + $attrs: i => false ? 0 : i.attrs, + $slots: i => false ? 0 : i.slots, + $refs: i => false ? 0 : i.refs, + $parent: i => getPublicInstance(i.parent), + $root: i => getPublicInstance(i.root), + $host: i => i.ce, + $emit: i => i.emit, + $options: i => true ? resolveMergedOptions(i) : 0, + $forceUpdate: i => i.f || (i.f = () => { + queueJob(i.update); + }), + $nextTick: i => i.n || (i.n = nextTick.bind(i.proxy)), + $watch: i => true ? instanceWatch.bind(i) : 0 +}); +const isReservedPrefix = key => key === "_" || key === "$"; +const hasSetupBinding = (state, key) => state !== _vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .EMPTY_OBJ */ .MZ && !state.__isScriptSetup && (0,_vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .hasOwn */ .$3)(state, key); +const PublicInstanceProxyHandlers = { + get({ + _: instance + }, key) { + if (key === "__v_skip") { + return true; + } + const { + ctx, + setupState, + data, + props, + accessCache, + type, + appContext + } = instance; + if (false) // removed by dead control flow +{} + let normalizedProps; + if (key[0] !== "$") { + const n = accessCache[key]; + if (n !== void 0) { + switch (n) { + case 1 /* SETUP */: + return setupState[key]; + case 2 /* DATA */: + return data[key]; + case 4 /* CONTEXT */: + return ctx[key]; + case 3 /* PROPS */: + return props[key]; + } + } else if (hasSetupBinding(setupState, key)) { + accessCache[key] = 1 /* SETUP */; + return setupState[key]; + } else if (data !== _vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .EMPTY_OBJ */ .MZ && (0,_vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .hasOwn */ .$3)(data, key)) { + accessCache[key] = 2 /* DATA */; + return data[key]; + } else if ( + // only cache other properties when instance has declared (thus stable) + // props + (normalizedProps = instance.propsOptions[0]) && (0,_vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .hasOwn */ .$3)(normalizedProps, key)) { + accessCache[key] = 3 /* PROPS */; + return props[key]; + } else if (ctx !== _vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .EMPTY_OBJ */ .MZ && (0,_vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .hasOwn */ .$3)(ctx, key)) { + accessCache[key] = 4 /* CONTEXT */; + return ctx[key]; + } else if ( false || shouldCacheAccess) { + accessCache[key] = 0 /* OTHER */; + } + } + const publicGetter = publicPropertiesMap[key]; + let cssModule, globalProperties; + if (publicGetter) { + if (key === "$attrs") { + (0,_vue_reactivity__WEBPACK_IMPORTED_MODULE_20__/* .track */ .u4)(instance.attrs, "get", ""); + false && 0; + } else if (false) // removed by dead control flow +{} + return publicGetter(instance); + } else if ( + // css module (injected by vue-loader) + (cssModule = type.__cssModules) && (cssModule = cssModule[key])) { + return cssModule; + } else if (ctx !== _vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .EMPTY_OBJ */ .MZ && (0,_vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .hasOwn */ .$3)(ctx, key)) { + accessCache[key] = 4 /* CONTEXT */; + return ctx[key]; + } else if ( + // global properties + globalProperties = appContext.config.globalProperties, (0,_vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .hasOwn */ .$3)(globalProperties, key)) { + { + return globalProperties[key]; + } + } else if (false) // removed by dead control flow +{} + }, + set({ + _: instance + }, key, value) { + const { + data, + setupState, + ctx + } = instance; + if (hasSetupBinding(setupState, key)) { + setupState[key] = value; + return true; + } else if (false) // removed by dead control flow +{} else if (data !== _vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .EMPTY_OBJ */ .MZ && (0,_vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .hasOwn */ .$3)(data, key)) { + data[key] = value; + return true; + } else if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .hasOwn */ .$3)(instance.props, key)) { + false && 0; + return false; + } + if (key[0] === "$" && key.slice(1) in instance) { + false && 0; + return false; + } else { + if (false) // removed by dead control flow +{} else { + ctx[key] = value; + } + } + return true; + }, + has({ + _: { + data, + setupState, + accessCache, + ctx, + appContext, + propsOptions + } + }, key) { + let normalizedProps; + return !!accessCache[key] || data !== _vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .EMPTY_OBJ */ .MZ && (0,_vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .hasOwn */ .$3)(data, key) || hasSetupBinding(setupState, key) || (normalizedProps = propsOptions[0]) && (0,_vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .hasOwn */ .$3)(normalizedProps, key) || (0,_vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .hasOwn */ .$3)(ctx, key) || (0,_vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .hasOwn */ .$3)(publicPropertiesMap, key) || (0,_vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .hasOwn */ .$3)(appContext.config.globalProperties, key); + }, + defineProperty(target, key, descriptor) { + if (descriptor.get != null) { + target._.accessCache[key] = 0; + } else if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_21__/* .hasOwn */ .$3)(descriptor, "value")) { + this.set(target, key, descriptor.value, null); + } + return Reflect.defineProperty(target, key, descriptor); + } +}; +if (false) // removed by dead control flow +{} +const RuntimeCompiledPublicInstanceProxyHandlers = /* @__PURE__ */(/* unused pure expression or super */ null && (extend({}, PublicInstanceProxyHandlers, { + get(target, key) { + if (key === Symbol.unscopables) { + return; + } + return PublicInstanceProxyHandlers.get(target, key, target); + }, + has(_, key) { + const has = key[0] !== "_" && !isGloballyAllowed(key); + if (false) // removed by dead control flow +{} + return has; + } +}))); +function createDevRenderContext(instance) { + const target = {}; + Object.defineProperty(target, `_`, { + configurable: true, + enumerable: false, + get: () => instance + }); + Object.keys(publicPropertiesMap).forEach(key => { + Object.defineProperty(target, key, { + configurable: true, + enumerable: false, + get: () => publicPropertiesMap[key](instance), + // intercepted by the proxy so no need for implementation, + // but needed to prevent set errors + set: NOOP + }); + }); + return target; +} +function exposePropsOnRenderContext(instance) { + const { + ctx, + propsOptions: [propsOptions] + } = instance; + if (propsOptions) { + Object.keys(propsOptions).forEach(key => { + Object.defineProperty(ctx, key, { + enumerable: true, + configurable: true, + get: () => instance.props[key], + set: NOOP + }); + }); + } +} +function exposeSetupStateOnRenderContext(instance) { + const { + ctx, + setupState + } = instance; + Object.keys(toRaw(setupState)).forEach(key => { + if (!setupState.__isScriptSetup) { + if (isReservedPrefix(key[0])) { + warn$1(`setup() return property ${JSON.stringify(key)} should not start with "$" or "_" which are reserved prefixes for Vue internals.`); + return; + } + Object.defineProperty(ctx, key, { + enumerable: true, + configurable: true, + get: () => setupState[key], + set: NOOP + }); + } + }); +} +const warnRuntimeUsage = method => warn$1(`${method}() is a compiler-hint helper that is only usable inside + + + +
+ + diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..7fad392 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,17 @@ + + + diff --git a/src/components/ChatShell.vue b/src/components/ChatShell.vue new file mode 100644 index 0000000..87d9458 --- /dev/null +++ b/src/components/ChatShell.vue @@ -0,0 +1,34 @@ + + + diff --git a/src/components/DocumentedShell.vue b/src/components/DocumentedShell.vue new file mode 100644 index 0000000..bce1bc9 --- /dev/null +++ b/src/components/DocumentedShell.vue @@ -0,0 +1,186 @@ + + + diff --git a/src/components/LoginView.vue b/src/components/LoginView.vue new file mode 100644 index 0000000..50ce114 --- /dev/null +++ b/src/components/LoginView.vue @@ -0,0 +1,502 @@ + + + diff --git a/src/components/chat/ChatPanel.vue b/src/components/chat/ChatPanel.vue new file mode 100644 index 0000000..c320160 --- /dev/null +++ b/src/components/chat/ChatPanel.vue @@ -0,0 +1,1067 @@ + + + diff --git a/src/components/chat/MessageComposer.vue b/src/components/chat/MessageComposer.vue new file mode 100644 index 0000000..84b3190 --- /dev/null +++ b/src/components/chat/MessageComposer.vue @@ -0,0 +1,223 @@ + + + diff --git a/src/components/chat/MessageList.vue b/src/components/chat/MessageList.vue new file mode 100644 index 0000000..ecb9cd7 --- /dev/null +++ b/src/components/chat/MessageList.vue @@ -0,0 +1,757 @@ + + + diff --git a/src/components/contacts/ContactPanel.vue b/src/components/contacts/ContactPanel.vue new file mode 100644 index 0000000..cbacee5 --- /dev/null +++ b/src/components/contacts/ContactPanel.vue @@ -0,0 +1,111 @@ + + + diff --git a/src/components/favorites/FavoriteList.vue b/src/components/favorites/FavoriteList.vue new file mode 100644 index 0000000..6b03949 --- /dev/null +++ b/src/components/favorites/FavoriteList.vue @@ -0,0 +1,79 @@ + + + diff --git a/src/components/favorites/FavoritePanel.vue b/src/components/favorites/FavoritePanel.vue new file mode 100644 index 0000000..c46adbb --- /dev/null +++ b/src/components/favorites/FavoritePanel.vue @@ -0,0 +1,202 @@ + + + diff --git a/src/components/sidebar/ContactList.vue b/src/components/sidebar/ContactList.vue new file mode 100644 index 0000000..dbb4108 --- /dev/null +++ b/src/components/sidebar/ContactList.vue @@ -0,0 +1,103 @@ + + + diff --git a/src/components/sidebar/ConversationList.vue b/src/components/sidebar/ConversationList.vue new file mode 100644 index 0000000..00b6673 --- /dev/null +++ b/src/components/sidebar/ConversationList.vue @@ -0,0 +1,525 @@ + + + diff --git a/src/components/sidebar/NavigationRail.vue b/src/components/sidebar/NavigationRail.vue new file mode 100644 index 0000000..95fad26 --- /dev/null +++ b/src/components/sidebar/NavigationRail.vue @@ -0,0 +1,724 @@ + + + diff --git a/src/config.js b/src/config.js new file mode 100644 index 0000000..0114e8f --- /dev/null +++ b/src/config.js @@ -0,0 +1,36 @@ +const currentHost = window.location.hostname || '127.0.0.1'; +const runtimeConfig = window.JC_WEB_IM_CONFIG || {}; +const appServerOrigin = `${window.location.protocol}//${currentHost}:8888`; +const qrAppServerOrigin = `${window.location.protocol}//${currentHost}:18888`; +const adminServerOrigin = `${window.location.protocol}//${currentHost}:8024`; + +export const appConfig = { + appName: 'Web IM', + adminServer: runtimeConfig.adminServer || process.env.VUE_APP_ADMIN_SERVER || adminServerOrigin, + appServer: runtimeConfig.appServer || process.env.VUE_APP_APP_SERVER || appServerOrigin, + qrAppServer: runtimeConfig.qrAppServer || process.env.VUE_APP_QR_APP_SERVER || qrAppServerOrigin, + platformConfigApi: runtimeConfig.platformConfigApi || process.env.VUE_APP_PLATFORM_CONFIG_API || (process.env.NODE_ENV === 'development' ? '/local-api/platform_config.php' : '/api/platform_config.php'), + registerConfigApi: runtimeConfig.registerConfigApi || process.env.VUE_APP_REGISTER_CONFIG_API || `${adminServerOrigin}/api/register-config`, + registerPhoneExistsApi: runtimeConfig.registerPhoneExistsApi || process.env.VUE_APP_REGISTER_PHONE_EXISTS_API || `${adminServerOrigin}/api/register-phone-exists`, + contactsApi: runtimeConfig.contactsApi || process.env.VUE_APP_CONTACTS_API || (process.env.NODE_ENV === 'development' ? '/local-api/contacts.php' : '/api/contacts.php'), + updateAliasApi: runtimeConfig.updateAliasApi || process.env.VUE_APP_UPDATE_ALIAS_API || (process.env.NODE_ENV === 'development' ? '/local-api/update_alias.php' : '/api/update_alias.php'), + addContactApi: runtimeConfig.addContactApi || process.env.VUE_APP_ADD_CONTACT_API || (process.env.NODE_ENV === 'development' ? '/local-api/add_contact.php' : '/api/add_contact.php'), + conversationActionsApi: runtimeConfig.conversationActionsApi || process.env.VUE_APP_CONVERSATION_ACTIONS_API || (process.env.NODE_ENV === 'development' ? '/local-api/conversation_actions.php' : '/api/conversation_actions.php'), + sendMessageApi: runtimeConfig.sendMessageApi || process.env.VUE_APP_SEND_MESSAGE_API || (process.env.NODE_ENV === 'development' ? '/local-api/send_message.php' : '/api/send_message.php'), + messageActionApi: runtimeConfig.messageActionApi || process.env.VUE_APP_MESSAGE_ACTION_API || (process.env.NODE_ENV === 'development' ? '/local-api/message_action.php' : '/api/message_action.php'), + messageDetailApi: runtimeConfig.messageDetailApi || process.env.VUE_APP_MESSAGE_DETAIL_API || (process.env.NODE_ENV === 'development' ? '/local-api/message_detail.php' : '/api/message_detail.php'), + messagesApi: runtimeConfig.messagesApi || process.env.VUE_APP_MESSAGES_API || (process.env.NODE_ENV === 'development' ? '/local-api/messages.php' : '/api/messages.php'), + profileApi: runtimeConfig.profileApi || process.env.VUE_APP_PROFILE_API || (process.env.NODE_ENV === 'development' ? '/local-api/profile.php' : '/api/profile.php'), + changePasswordApi: runtimeConfig.changePasswordApi || process.env.VUE_APP_CHANGE_PASSWORD_API || `${appServerOrigin}/auth/changePassword`, + logoutApi: runtimeConfig.logoutApi || process.env.VUE_APP_LOGOUT_API || `${appServerOrigin}/auth/logout`, + imServerHost: runtimeConfig.imServerHost || process.env.VUE_APP_IM_SERVER_HOST || currentHost, + imRoutePort: Number(runtimeConfig.imRoutePort || process.env.VUE_APP_IM_ROUTE_PORT || 18000), + useWss: runtimeConfig.useWss ?? process.env.VUE_APP_USE_WSS === 'true', + wsPath: runtimeConfig.wsPath || process.env.VUE_APP_WS_PATH || '/ws', + wsUrl: runtimeConfig.wsUrl || process.env.VUE_APP_WS_URL || '', + sdkMode: runtimeConfig.sdkMode || process.env.VUE_APP_IM_MODE || 'http', + syncIntervalMs: Number(runtimeConfig.syncIntervalMs || process.env.VUE_APP_SYNC_INTERVAL_MS || 300), + platform: 5, + clientIdStorageKey: 'qmt-web-im-client-id', + authTokenStoragePrefix: 'qmt-web-im-auth-token-' +}; diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..4ce2b30 --- /dev/null +++ b/src/main.js @@ -0,0 +1,6 @@ +import { createApp } from 'vue'; +import { createPinia } from 'pinia'; +import App from './App.vue'; +import './styles/base.css'; + +createApp(App).use(createPinia()).mount('#app'); diff --git a/src/services/appServerClient.js b/src/services/appServerClient.js new file mode 100644 index 0000000..214e3f2 --- /dev/null +++ b/src/services/appServerClient.js @@ -0,0 +1,266 @@ +import axios from 'axios'; +import { appConfig } from '@/config'; + +const AES_KEY = 'xczx2026!@#secKe'; +const ENCRYPTION_EXCLUDE_PATHS = ['/avatar/', '/f/', '/amr2mp3', '/appVersion/check']; +const AUTH_STORAGE_KEY = 'qmt-web-im-app-auth-v1'; +const AUTH_ERROR_CODE = 13; +const REFRESH_ERROR_CODE = 20045; + +let importedCryptoKeyPromise = null; +let refreshPromise = null; + +function storageScope() { + return appConfig.appServer || ''; +} + +function readAuthStore() { + try { + return JSON.parse(localStorage.getItem(AUTH_STORAGE_KEY) || '{}'); + } catch (error) { + return {}; + } +} + +function writeAuthStore(store) { + localStorage.setItem(AUTH_STORAGE_KEY, JSON.stringify(store)); +} + +function normalizeAuthBundle(bundle = {}) { + const accessToken = String(bundle.accessToken || '').trim(); + const refreshToken = String(bundle.refreshToken || '').trim(); + if (!accessToken || !refreshToken) { + return null; + } + return { + accessToken, + refreshToken, + tokenType: String(bundle.tokenType || 'Bearer').trim() || 'Bearer', + accessTokenExpireIn: Number(bundle.accessTokenExpireIn || 0) || 0, + accessTokenExpireAt: Number(bundle.accessTokenExpireAt || 0) || 0, + refreshTokenExpireIn: Number(bundle.refreshTokenExpireIn || 0) || 0, + refreshTokenExpireAt: Number(bundle.refreshTokenExpireAt || 0) || 0 + }; +} + +export function getStoredAppAuth() { + const store = readAuthStore(); + return normalizeAuthBundle(store[storageScope()]); +} + +export function persistAppAuth(bundle) { + const normalized = normalizeAuthBundle(bundle); + const store = readAuthStore(); + if (!normalized) { + delete store[storageScope()]; + writeAuthStore(store); + return null; + } + store[storageScope()] = normalized; + writeAuthStore(store); + return normalized; +} + +export function clearStoredAppAuth() { + persistAppAuth(null); +} + +function hasFormData(value) { + return typeof FormData !== 'undefined' && value instanceof FormData; +} + +function absoluteUrl(url) { + return new URL(url, window.location.origin); +} + +function shouldEncrypt(url) { + const pathname = absoluteUrl(url).pathname; + return !ENCRYPTION_EXCLUDE_PATHS.some(path => pathname === path || pathname.startsWith(path)); +} + +function shouldAttachAuth(config) { + return !config.skipAuth; +} + +function textEncoder() { + return new TextEncoder(); +} + +function textDecoder() { + return new TextDecoder(); +} + +function bytesToBase64(bytes) { + let binary = ''; + bytes.forEach(byte => { + binary += String.fromCharCode(byte); + }); + return btoa(binary); +} + +function base64ToBytes(base64) { + const binary = atob(base64); + const bytes = new Uint8Array(binary.length); + for (let index = 0; index < binary.length; index += 1) { + bytes[index] = binary.charCodeAt(index); + } + return bytes; +} + +async function getCryptoKey() { + if (!importedCryptoKeyPromise) { + importedCryptoKeyPromise = window.crypto.subtle.importKey( + 'raw', + textEncoder().encode(AES_KEY), + { name: 'AES-CBC' }, + false, + ['encrypt', 'decrypt'] + ); + } + return importedCryptoKeyPromise; +} + +async function encryptPayload(plainText) { + const iv = window.crypto.getRandomValues(new Uint8Array(16)); + const key = await getCryptoKey(); + const encrypted = await window.crypto.subtle.encrypt( + { name: 'AES-CBC', iv }, + key, + textEncoder().encode(plainText) + ); + return `${bytesToBase64(iv)}:${bytesToBase64(new Uint8Array(encrypted))}`; +} + +async function decryptPayload(cipherText) { + const [ivBase64, encryptedBase64] = String(cipherText || '').split(':', 2); + if (!ivBase64 || !encryptedBase64) { + throw new Error('Encrypted payload format is invalid'); + } + const key = await getCryptoKey(); + const decrypted = await window.crypto.subtle.decrypt( + { name: 'AES-CBC', iv: base64ToBytes(ivBase64) }, + key, + base64ToBytes(encryptedBase64) + ); + return textDecoder().decode(decrypted); +} + +function normalizeBusinessResponse(payload) { + if (payload && typeof payload === 'object' && !Array.isArray(payload)) { + return payload; + } + return { + code: 0, + message: 'success', + data: payload + }; +} + +async function decodeResponsePayload(response) { + const encrypted = String(response.headers?.['x-encrypted'] || '').toLowerCase() === 'true'; + if (encrypted && typeof response.data === 'string') { + const plainText = await decryptPayload(response.data); + response.data = normalizeBusinessResponse(JSON.parse(plainText)); + return response; + } + response.data = normalizeBusinessResponse(response.data); + return response; +} + +const client = axios.create({ + withCredentials: false +}); + +client.interceptors.request.use(async config => { + const nextConfig = { ...config }; + const url = absoluteUrl(nextConfig.url || '').toString(); + + if (shouldAttachAuth(nextConfig)) { + const auth = getStoredAppAuth(); + if (auth?.accessToken) { + nextConfig.headers = { + ...(nextConfig.headers || {}), + Authorization: `${auth.tokenType || 'Bearer'} ${auth.accessToken}` + }; + } + } + + if (!hasFormData(nextConfig.data) && nextConfig.method?.toLowerCase() !== 'get' && shouldEncrypt(url)) { + const plainBody = JSON.stringify(nextConfig.data ?? {}); + nextConfig.data = await encryptPayload(plainBody); + nextConfig.headers = { + ...(nextConfig.headers || {}), + 'Content-Type': 'text/plain;charset=UTF-8', + 'X-Timestamp': `${Math.floor(Date.now() / 1000)}` + }; + } + + return nextConfig; +}); + +client.interceptors.response.use(async response => decodeResponsePayload(response)); + +async function refreshAccessToken() { + if (refreshPromise) { + return refreshPromise; + } + const auth = getStoredAppAuth(); + if (!auth?.refreshToken) { + clearStoredAppAuth(); + throw new Error('登录状态已失效,请重新登录'); + } + + refreshPromise = client.post(`${appConfig.appServer}/auth/refreshToken`, { + refreshToken: auth.refreshToken + }, { + skipAuth: true + }).then(response => { + const payload = response.data || {}; + if (Number(payload.code) !== 0 || !payload.data) { + if (Number(payload.code) === REFRESH_ERROR_CODE) { + clearStoredAppAuth(); + } + throw new Error(payload.message || '刷新登录状态失败'); + } + persistAppAuth(payload.data); + return payload.data; + }).finally(() => { + refreshPromise = null; + }); + + return refreshPromise; +} + +async function request(config) { + const response = await client.request(config); + const payload = response.data || {}; + if (Number(payload.code) === AUTH_ERROR_CODE && shouldAttachAuth(config) && !config._retriedAfterRefresh) { + await refreshAccessToken(); + return request({ + ...config, + _retriedAfterRefresh: true + }); + } + return response; +} + +export async function appServerRequest(config) { + return request(config); +} + +export async function appServerGet(url, config = {}) { + return request({ + ...config, + method: 'get', + url + }); +} + +export async function appServerPost(url, data = {}, config = {}) { + return request({ + ...config, + method: 'post', + url, + data + }); +} diff --git a/src/services/documentedHttpImClient.js b/src/services/documentedHttpImClient.js new file mode 100644 index 0000000..7e6a546 --- /dev/null +++ b/src/services/documentedHttpImClient.js @@ -0,0 +1,273 @@ +import { appConfig } from '@/config'; +import { HttpImClient } from './httpImClient'; +import axios from 'axios'; +import { + appServerGet, + appServerPost, + clearStoredAppAuth, + getStoredAppAuth, + persistAppAuth +} from './appServerClient'; + +function inferAccountType(account) { + return String(account || '').includes('@') ? 2 : 1; +} + +function unwrapData(response) { + return response.data?.data ?? response.data?.result ?? null; +} + +function unwrapMessage(response, fallback) { + return response.data?.message || response.data?.msg || fallback; +} + +export class DocumentedHttpImClient extends HttpImClient { + unsupported(feature) { + throw new Error(`当前对接文档未提供“${feature}”接口,已禁用旧本地代理链路`); + } + + async loginWithPassword(account, password, type = inferAccountType(account)) { + const response = await appServerPost(`${appConfig.appServer}/auth/login_pwd`, { + type, + number: account, + password, + platform: appConfig.platform, + clientId: this.getClientId() + }, { + skipAuth: true + }); + + if (response.data?.code !== 0) { + throw new Error(unwrapMessage(response, '登录失败')); + } + + persistAppAuth(unwrapData(response)); + return unwrapData(response); + } + + async requestAuthCode(email) { + const response = await appServerPost(`${appConfig.appServer}/auth/sendEmailCode`, { + email + }, { + skipAuth: true + }); + + if (response.data?.code !== 0) { + throw new Error(unwrapMessage(response, '验证码发送失败')); + } + + return unwrapData(response); + } + + async loginWithCode(account, code) { + void account; + void code; + throw new Error('接口文档未提供验证码登录接口,请使用密码登录'); + } + + async register({ type, number, code = '', password, invitationCode = '' }) { + const response = await appServerPost(`${appConfig.appServer}/auth/register`, { + type, + number, + code, + password, + clientId: this.getClientId(), + platform: appConfig.platform, + invitationCode + }, { + skipAuth: true + }); + + if (response.data?.code !== 0) { + throw new Error(unwrapMessage(response, '注册失败')); + } + + persistAppAuth(unwrapData(response)); + return unwrapData(response); + } + + async createPcSession(userId = '') { + const response = await axios.post(`${appConfig.qrAppServer}/pc_session`, { + flag: 1, + device_name: 'Web IM', + userId, + clientId: this.getClientId(), + platform: appConfig.platform + }, { withCredentials: false }); + + if (response.data?.code !== 0) { + throw new Error(unwrapMessage(response, '二维码登录会话创建失败')); + } + + return unwrapData(response) || {}; + } + + async loginWithPcSession(token) { + const url = `${appConfig.qrAppServer}/session_login/${encodeURIComponent(token)}`; + let response; + try { + response = await axios.post(url, null, { withCredentials: false }); + } catch (postError) { + response = await axios.get(url, { withCredentials: false }); + } + + this.persistAppToken(url, response); + + const payload = response.data || {}; + const result = payload.result ?? payload.data ?? null; + + if (Number(payload.code) === 0 && result) { + persistAppAuth(result); + } + + if ([0, 8, 9, 10, 13, 18, 20042].includes(Number(payload.code))) { + return { + ...payload, + data: result + }; + } + + throw new Error(payload.message || '扫码登录失败'); + } + + async changePassword({ oldPassword, newPassword }) { + const response = await appServerPost(appConfig.changePasswordApi, { + oldPassword, + newPassword + }); + + if (response.data?.code !== 0) { + throw new Error(unwrapMessage(response, '密码修改失败')); + } + + return true; + } + + async resetPasswordWithCode({ mobile, resetCode, newPassword }) { + void mobile; + void resetCode; + void newPassword; + throw new Error('接口文档未提供重置密码接口,请联系服务端确认流程'); + } + + async logout() { + const auth = getStoredAppAuth(); + if (!auth?.refreshToken) { + clearStoredAppAuth(); + return true; + } + + try { + const response = await appServerPost(appConfig.logoutApi, { + refreshToken: auth.refreshToken + }); + if (response.data?.code !== 0) { + throw new Error(unwrapMessage(response, '退出登录失败')); + } + } finally { + clearStoredAppAuth(); + } + + return true; + } + + async fetchRemoteConversations() { + return []; + } + + async fetchRemoteMessages() { + return []; + } + + async getServerContactList() { + return []; + } + + async getProfile() { + return null; + } + + async sendTextMessage() { + this.unsupported('发送消息'); + } + + async sendAttachmentMessage() { + this.unsupported('发送附件消息'); + } + + async addContact() { + this.unsupported('添加联系人'); + } + + async updateContactAlias() { + this.unsupported('联系人备注'); + } + + async createGroup() { + this.unsupported('创建群组'); + } + + async joinGroup() { + this.unsupported('加入群组'); + } + + async broadcastMessage() { + this.unsupported('群发消息'); + } + + async getGroupDetail() { + this.unsupported('群详情'); + } + + async getGroupMembers() { + this.unsupported('群成员'); + } + + async updateGroupAlias() { + this.unsupported('群备注'); + } + + async leaveGroup() { + this.unsupported('退出群组'); + } + + async dismissGroup() { + this.unsupported('解散群组'); + } + + async transferGroup() { + this.unsupported('转让群组'); + } + + async setGroupAdmins() { + this.unsupported('设置群管理员'); + } + + async updateGroupProfile() { + this.unsupported('更新群资料'); + } + + async kickGroupMembers() { + this.unsupported('移除群成员'); + } + + async updateGroupMuteSetting() { + this.unsupported('群禁言设置'); + } + + async updateProfile() { + this.unsupported('更新个人资料'); + } + + async recallMessage() { + this.unsupported('撤回消息'); + } + + async editMessage() { + this.unsupported('编辑消息'); + } + + async getMessageDetail() { + this.unsupported('消息详情'); + } +} diff --git a/src/services/httpImClient.js b/src/services/httpImClient.js new file mode 100644 index 0000000..5ea697b --- /dev/null +++ b/src/services/httpImClient.js @@ -0,0 +1,1069 @@ +import axios from 'axios'; +import { appConfig } from '@/config'; +import { + appServerGet, + appServerPost, + clearStoredAppAuth, + getStoredAppAuth, + persistAppAuth +} from '@/services/appServerClient'; + +const LOCAL_MESSAGE_STORAGE_KEY = 'qmt-web-im-local-messages-v2'; +const LOCAL_HIDDEN_MESSAGE_STORAGE_KEY = 'qmt-web-im-hidden-messages-v1'; +const LOCAL_RECALLED_MESSAGE_STORAGE_KEY = 'qmt-web-im-recalled-messages-v1'; + +function scopedStorageKey(baseKey, userId = '') { + const scope = `${appConfig.appServer || ''}:${userId || 'anonymous'}`; + return `${baseKey}:${encodeURIComponent(scope)}`; +} + +function randomId() { + if (window.crypto && window.crypto.randomUUID) { + return window.crypto.randomUUID().replaceAll('-', ''); + } + return `${Date.now()}${Math.random().toString(16).slice(2)}`; +} + +function readLocalMessages(userId) { + try { + return JSON.parse(localStorage.getItem(scopedStorageKey(LOCAL_MESSAGE_STORAGE_KEY, userId)) || '{}'); + } catch (error) { + return {}; + } +} + +function readMessageSet(storageKey, userId) { + try { + return JSON.parse(localStorage.getItem(scopedStorageKey(storageKey, userId)) || '{}'); + } catch (error) { + return {}; + } +} + +function writeMessageSet(storageKey, messagesByConversation, userId) { + localStorage.setItem(scopedStorageKey(storageKey, userId), JSON.stringify(messagesByConversation)); +} + +function messageKeys(message) { + return [message?.id, message?.messageUid] + .filter(Boolean) + .map(value => String(value)); +} + +function hasStoredMessage(storageKey, conversationId, message, userId) { + const stored = readMessageSet(storageKey, userId); + const ids = new Set(stored[conversationId] || []); + return messageKeys(message).some(id => ids.has(id)); +} + +function storeMessageKeys(storageKey, conversationId, messages, userId) { + const stored = readMessageSet(storageKey, userId); + const ids = new Set(stored[conversationId] || []); + messages.forEach(message => { + messageKeys(message).forEach(id => ids.add(id)); + }); + stored[conversationId] = Array.from(ids); + writeMessageSet(storageKey, stored, userId); +} + +function withRecalledState(message) { + return { + ...message, + status: 'recalled', + deliveryStatus: '', + recalled: true, + recallStatus: '已撤回' + }; +} + +function applyLocalMessageState(conversationId, messages, userId) { + return messages + .filter(message => !hasStoredMessage(LOCAL_HIDDEN_MESSAGE_STORAGE_KEY, conversationId, message, userId)) + .map(message => { + const isRecalled = Boolean(message.recalled) + || message.status === 'recalled' + || hasStoredMessage(LOCAL_RECALLED_MESSAGE_STORAGE_KEY, conversationId, message, userId); + if (!isRecalled) { + return message; + } + return withRecalledState(message); + }); +} + +function writeLocalMessages(messagesByConversation, userId) { + localStorage.setItem(scopedStorageKey(LOCAL_MESSAGE_STORAGE_KEY, userId), JSON.stringify(messagesByConversation)); +} + +function saveLocalMessage(message, userId) { + const messagesByConversation = readLocalMessages(userId); + messagesByConversation[message.conversationId] = messagesByConversation[message.conversationId] || []; + const index = messagesByConversation[message.conversationId].findIndex(item => item.id === message.id); + if (index >= 0) { + messagesByConversation[message.conversationId].splice(index, 1, message); + } else { + messagesByConversation[message.conversationId].push(message); + } + writeLocalMessages(messagesByConversation, userId); +} + +function mergeSyncedMessages(localMessages, remoteMessages, conversationId, userId) { + const visibleLocalMessages = applyLocalMessageState(conversationId, localMessages, userId); + const localByKey = new Map(); + visibleLocalMessages.forEach(message => { + messageKeys(message).forEach(key => { + if (!localByKey.has(key)) { + localByKey.set(key, message); + } + }); + }); + + const visibleRemoteMessages = applyLocalMessageState(conversationId, remoteMessages, userId).map(remoteMessage => { + if (!remoteMessage.recalled || String(remoteMessage.text || '').trim()) { + return remoteMessage; + } + const localMessage = messageKeys(remoteMessage) + .map(key => localByKey.get(key)) + .find(message => message?.text && message.text !== '你撤回了一条消息'); + return localMessage ? { ...remoteMessage, text: localMessage.text } : remoteMessage; + }); + const remoteKeys = new Set(); + visibleRemoteMessages.forEach(message => { + messageKeys(message).forEach(key => remoteKeys.add(key)); + }); + const now = Date.now(); + + function hasRemoteMatch(localMessage) { + if (localMessage.messageUid && remoteKeys.has(localMessage.messageUid)) { + return true; + } + return visibleRemoteMessages.some(remoteMessage => { + return remoteMessage.direction === localMessage.direction + && remoteMessage.text === localMessage.text + && Math.abs(Number(remoteMessage.timestamp || 0) - Number(localMessage.timestamp || 0)) < 15000; + }); + } + + const pendingLocalMessages = visibleLocalMessages.filter(message => { + if (message.status === 'recalled' || message.recalled) { + return message.direction === 'out' && !hasRemoteMatch(message); + } + if (message.direction !== 'out') { + return false; + } + if (message.status === 'sent' && message.messageUid) { + return !hasRemoteMatch(message); + } + if (message.status === 'sent') { + return !hasRemoteMatch(message) && now - Number(message.timestamp || 0) < 30000; + } + return message.status === 'sending' || message.status === 'failed'; + }); + + return [...visibleRemoteMessages, ...pendingLocalMessages].sort((a, b) => { + return Number(a.displayTimestamp || a.timestamp || 0) - Number(b.displayTimestamp || b.timestamp || 0); + }); +} + +function getTitle(value, fallback) { + return value || fallback || ''; +} + +function getUniqueEquipment(mobile, fallback) { + const allowedDevices = { + '18301902119': '67072d37062c4f31b1165073552933ba', + '18301902116': '1777479418113eeea8e236edce', + '18301902118': 'de89fb2f2a61480a8c381f6c16e28bd8', + '18301902117': 'd117f480faa64145b6162107b1b49617' + }; + return allowedDevices[mobile] || localStorage.getItem('uniqueEquipment') || fallback; +} + +function canUseLocalLoginFallback(response) { + const message = response.data?.message || ''; + return process.env.NODE_ENV === 'development' && message.includes('允许此设备登录'); +} + +export class HttpImClient { + constructor() { + this.listeners = new Map(); + this.connected = false; + this.currentUser = null; + } + + getClientId() { + let clientId = localStorage.getItem(appConfig.clientIdStorageKey); + if (!clientId) { + clientId = randomId(); + localStorage.setItem(appConfig.clientIdStorageKey, clientId); + } + return clientId; + } + + restoreClientId(clientId) { + if (clientId) { + localStorage.setItem(appConfig.clientIdStorageKey, clientId); + } + } + + getSdkVersion() { + return 'HTTP'; + } + + async ensureSdk() { + return true; + } + + async loginWithPassword(mobile, password) { + const url = `${appConfig.appServer}/login_pwd`; + const deviceId = getUniqueEquipment(mobile, this.getClientId()); + const response = await axios.post(url, { + mobile, + password, + platform: appConfig.platform, + clientId: deviceId, + uniqueEquipment: deviceId + }, { withCredentials: false }); + + this.persistAppToken(url, response); + if (response.data?.code !== 0) { + if (canUseLocalLoginFallback(response)) { + return { + userId: mobile, + token: `local-device-pending-${Date.now()}`, + displayName: mobile, + localOnly: true + }; + } + throw new Error(response.data?.message || '登录失败'); + } + return response.data.result; + } + + async requestAuthCode(mobile) { + const response = await axios.post(`${appConfig.appServer}/send_code`, { mobile }, { withCredentials: false }); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '验证码发送失败'); + } + return response.data.result; + } + + async loginWithCode(mobile, code) { + const url = `${appConfig.appServer}/login`; + const deviceId = getUniqueEquipment(mobile, this.getClientId()); + const response = await axios.post(url, { + mobile, + code, + platform: appConfig.platform, + clientId: deviceId, + uniqueEquipment: deviceId + }, { withCredentials: false }); + + this.persistAppToken(url, response); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '登录失败'); + } + return response.data.result; + } + + async createPcSession(userId = '') { + const response = await axios.post(`${appConfig.appServer}/pc_session`, { + flag: 1, + device_name: 'Web IM', + userId, + clientId: this.getClientId(), + platform: appConfig.platform + }, { withCredentials: false }); + + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '二维码登录会话创建失败'); + } + return response.data.result || {}; + } + + async loginWithPcSession(token) { + const url = `${appConfig.appServer}/session_login/${encodeURIComponent(token)}`; + const response = await axios.post(url, null, { withCredentials: false }); + this.persistAppToken(url, response); + const data = response.data || {}; + if ([0, 8, 9, 10, 18].includes(Number(data.code))) { + return data; + } + throw new Error(data.message || '扫码登录失败'); + } + + async registerWithPhone(mobile, code) { + const url = `${appConfig.appServer}/register`; + const deviceId = getUniqueEquipment(mobile, this.getClientId()); + + try { + const response = await axios.post(url, { + mobile, + code, + platform: appConfig.platform, + clientId: deviceId, + uniqueEquipment: deviceId + }, { withCredentials: false }); + + this.persistAppToken(url, response); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '注册失败'); + } + return response.data.result; + } catch (error) { + const status = error.response?.status; + if (status === 404 || status === 405) { + return this.loginWithCode(mobile, code); + } + throw error; + } + } + + connect(userId, token) { + this.currentUser = { userId, token }; + this.connected = true; + this.emit('connectionStatusChanged', { connected: true, statusText: '已连接' }); + this.refreshConversationList(); + } + + disconnect() { + this.connected = false; + this.currentUser = null; + this.emit('connectionStatusChanged', { connected: false, statusText: '未连接' }); + } + + getConversationList() { + return []; + } + + refreshConversationList() { + const conversations = this.getConversationList(); + if (conversations.length) { + this.emit('conversationsChanged', conversations); + } + if (this.currentUser?.userId) { + this.fetchRemoteConversations().then(remoteConversations => { + this.emit('conversationsChanged', remoteConversations); + }).catch(() => {}); + } + return conversations; + } + + async fetchRemoteConversations(limit = 50) { + if (!this.currentUser?.userId) { + return []; + } + const response = await axios.post(appConfig.messagesApi, { + userId: this.currentUser.userId, + limit + }, { withCredentials: false }); + + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '会话同步失败'); + } + + return (response.data?.result?.conversations || []).map(conversation => ({ + ...conversation, + updatedAt: Number(conversation.updatedAt || Date.now()), + avatarText: conversation.avatarText || String(conversation.title || conversation.target || '?').slice(0, 1).toUpperCase(), + raw: conversation.raw || { type: 'single', target: conversation.target, line: 0 } + })); + } + + getMessages(conversation) { + if (!conversation?.id) { + return []; + } + const userId = this.currentUser?.userId || ''; + return applyLocalMessageState(conversation.id, readLocalMessages(userId)[conversation.id] || [], userId); + } + + async fetchRemoteMessages(conversation, limit = 50, options = {}) { + if (!conversation?.id || !this.currentUser?.userId) { + return []; + } + const response = await axios.post(appConfig.messagesApi, { + userId: this.currentUser.userId, + target: conversation.target || conversation.raw?.target || conversation.id.replace(/^single-/, ''), + limit, + conversationType: conversation.type || conversation.raw?.type || 0, + markRead: Boolean(options.markRead) + }, { withCredentials: false }); + + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '消息同步失败'); + } + + const messages = (response.data?.result?.messages || []).map(message => ({ + ...message, + displayTimestamp: message.displayTimestamp || message.timestamp, + conversationId: conversation.id + })); + const userId = this.currentUser?.userId || ''; + const messagesByConversation = readLocalMessages(userId); + messagesByConversation[conversation.id] = mergeSyncedMessages(messagesByConversation[conversation.id] || [], messages, conversation.id, userId); + writeLocalMessages(messagesByConversation, userId); + return messagesByConversation[conversation.id]; + } + + hideMessages(conversation, messages) { + const items = Array.isArray(messages) ? messages : [messages]; + if (!conversation?.id || !items.length) { + return; + } + const userId = this.currentUser?.userId || ''; + storeMessageKeys(LOCAL_HIDDEN_MESSAGE_STORAGE_KEY, conversation.id, items, userId); + const messagesByConversation = readLocalMessages(userId); + messagesByConversation[conversation.id] = applyLocalMessageState( + conversation.id, + messagesByConversation[conversation.id] || [], + userId + ); + writeLocalMessages(messagesByConversation, userId); + } + + markMessagesRecalled(conversation, messages) { + const items = Array.isArray(messages) ? messages : [messages]; + if (!conversation?.id || !items.length) { + return; + } + const userId = this.currentUser?.userId || ''; + storeMessageKeys(LOCAL_RECALLED_MESSAGE_STORAGE_KEY, conversation.id, items, userId); + const messagesByConversation = readLocalMessages(userId); + messagesByConversation[conversation.id] = applyLocalMessageState( + conversation.id, + messagesByConversation[conversation.id] || [], + userId + ); + writeLocalMessages(messagesByConversation, userId); + } + + async recallMessage(conversation, message) { + const messageUid = String(message?.messageUid || message?.id || '').trim(); + if (!/^\d+$/.test(messageUid)) { + throw new Error('缺少可撤回的消息ID'); + } + + const response = await axios.post(appConfig.messageActionApi, { + action: 'recall', + userId: this.currentUser?.userId || message.from || '', + messageUid + }, { withCredentials: false }); + + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '撤回失败'); + } + this.markMessagesRecalled(conversation, [message]); + return response.data.result; + } + + async editMessage(conversation, message, text) { + const messageUid = String(message?.messageUid || message?.id || '').trim(); + if (!/^\d+$/.test(messageUid)) { + throw new Error('缺少可编辑的消息ID'); + } + const nextText = String(text || '').trim(); + if (!nextText) { + throw new Error('编辑内容不能为空'); + } + + const response = await axios.post(appConfig.messageActionApi, { + action: 'edit', + userId: this.currentUser?.userId || message.from || '', + messageUid, + text: nextText + }, { withCredentials: false }); + + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '编辑失败'); + } + + const result = response.data?.result?.message || {}; + const editedMessage = { + ...message, + text: result.text || nextText, + status: 'delivered', + deliveryStatus: 'delivered', + editedAt: Number(result.editedAt || Date.now()), + activityTimestamp: Number(result.editedAt || Date.now()) + }; + saveLocalMessage(editedMessage, this.currentUser?.userId || ''); + this.emit('messageStatusChanged', editedMessage); + return editedMessage; + } + + async getMessageDetail(conversation, message) { + const messageUid = String(message?.messageUid || message?.id || '').trim(); + if (!messageUid) { + throw new Error('缺少消息ID'); + } + const response = await axios.post(appConfig.messageDetailApi, { + userId: this.currentUser?.userId || '', + target: conversation?.target || conversation?.raw?.target || conversation?.id?.replace(/^single-/, '') || '', + conversationType: conversation?.type || conversation?.raw?.type || 'single', + messageUid + }, { withCredentials: false }); + + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '消息详情加载失败'); + } + + return response.data?.result?.detail || null; + } + + async sendTextMessage(conversation, text, initialMessage = null) { + const senderRole = conversation?.role || conversation?.raw?.role || ''; + const message = initialMessage || { + id: randomId(), + conversationId: conversation.id, + from: this.currentUser?.userId || 'me', + senderRole, + direction: 'out', + type: 'text', + text, + status: 'sending', + deliveryStatus: 'sending', + timestamp: Date.now() + }; + saveLocalMessage(message, this.currentUser?.userId || ''); + if (!initialMessage) { + this.emit('messageSent', message); + } + + try { + const response = await axios.post(appConfig.sendMessageApi, { + sender: this.currentUser?.userId || '', + target: conversation.target || conversation.raw?.target || conversation.id.replace(/^single-/, ''), + conversationType: conversation.type || conversation.raw?.type || 0, + text + }, { withCredentials: false }); + + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '发送失败'); + } + + const sentMessage = { + ...message, + status: 'sent', + deliveryStatus: 'sent', + messageUid: response.data?.result?.messageUid ? String(response.data.result.messageUid) : '', + senderRole: response.data?.result?.senderRole || message.senderRole || '', + serverTimestamp: Number(response.data?.result?.timestamp || 0) || null, + timestamp: message.timestamp, + displayTimestamp: message.displayTimestamp || message.timestamp + }; + saveLocalMessage(sentMessage, this.currentUser?.userId || ''); + this.emit('messageStatusChanged', sentMessage); + return sentMessage; + } catch (error) { + const failedMessage = { + ...message, + status: 'failed', + deliveryStatus: '', + error: error.response?.data?.message || error.message || '发送失败' + }; + saveLocalMessage(failedMessage, this.currentUser?.userId || ''); + this.emit('messageStatusChanged', failedMessage); + return failedMessage; + } + } + + async sendAttachmentMessage(conversation, { type, file, text = '' }, initialMessage = null) { + const senderRole = conversation?.role || conversation?.raw?.role || ''; + const message = initialMessage || { + id: randomId(), + conversationId: conversation.id, + from: this.currentUser?.userId || 'me', + senderRole, + direction: 'out', + type, + text, + attachment: { + type, + name: file?.name || (type === 'image' ? '图片' : '文件'), + mime: file?.type || '', + size: file?.size || 0, + url: type === 'image' && file ? URL.createObjectURL(file) : '' + }, + status: 'sending', + deliveryStatus: 'sending', + timestamp: Date.now() + }; + saveLocalMessage(message, this.currentUser?.userId || ''); + if (!initialMessage) { + this.emit('messageSent', message); + } + + try { + const formData = new FormData(); + formData.append('sender', this.currentUser?.userId || ''); + formData.append('target', conversation.target || conversation.raw?.target || conversation.id.replace(/^single-/, '')); + formData.append('conversationType', conversation.type || conversation.raw?.type || 0); + formData.append('messageType', type); + formData.append('text', text || ''); + formData.append('attachment', file); + + const response = await axios.post(appConfig.sendMessageApi, formData, { + withCredentials: false + }); + + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '发送失败'); + } + + const sentMessage = { + ...message, + status: 'sent', + deliveryStatus: 'sent', + messageUid: response.data?.result?.messageUid ? String(response.data.result.messageUid) : '', + senderRole: response.data?.result?.senderRole || message.senderRole || '', + attachment: response.data?.result?.attachment || message.attachment, + serverTimestamp: Number(response.data?.result?.timestamp || 0) || null, + timestamp: message.timestamp, + displayTimestamp: message.displayTimestamp || message.timestamp + }; + saveLocalMessage(sentMessage, this.currentUser?.userId || ''); + this.emit('messageStatusChanged', sentMessage); + return sentMessage; + } catch (error) { + const failedMessage = { + ...message, + status: 'failed', + deliveryStatus: '', + error: error.response?.data?.message || error.message || '发送失败' + }; + saveLocalMessage(failedMessage, this.currentUser?.userId || ''); + this.emit('messageStatusChanged', failedMessage); + return failedMessage; + } + } + + getContactList() { + return []; + } + + async getServerContactList(userId) { + if (!userId) { + return []; + } + const response = await axios.post(appConfig.contactsApi, { userId }, { + withCredentials: false, + headers: this.getAppAuthHeaders(appConfig.contactsApi) + }); + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '联系人加载失败'); + } + return (response.data?.result?.contacts || []).map(item => { + return this.mapContact(item.uid, { alias: item.alias }, { + uid: item.uid, + displayName: item.displayName, + name: item.name, + mobile: item.mobile, + portrait: item.portrait, + address: item.address, + friendAlias: item.alias + }); + }).sort((a, b) => a.title.localeCompare(b.title, 'zh-CN')); + } + + async addContact(userId, keyword, reason = '') { + const response = await axios.post(appConfig.addContactApi, { + userId, + keyword, + reason + }, { withCredentials: false }); + + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '添加联系人失败'); + } + + const item = response.data?.result?.contact; + if (!item?.uid) { + return null; + } + + return this.mapContact(item.uid, { alias: item.alias }, { + uid: item.uid, + displayName: item.displayName, + name: item.name, + mobile: item.mobile, + portrait: item.portrait, + address: item.address, + friendAlias: item.alias + }); + } + + normalizeConversation(conversation) { + const title = getTitle(conversation?.title, conversation?.target || ''); + return { + ...conversation, + id: conversation?.id || `${conversation?.type === 'single' ? 'single' : 'group'}-${conversation?.target}`, + target: conversation?.target || conversation?.raw?.target || '', + title, + subtitle: conversation?.subtitle || '', + unread: Number(conversation?.unread || 0), + portrait: conversation?.portrait || '', + avatarText: conversation?.avatarText || title.slice(0, 1).toUpperCase(), + updatedAt: Number(conversation?.updatedAt || Date.now()), + raw: conversation?.raw || { + type: conversation?.type === 'single' ? 'single' : 'group', + target: conversation?.target || '', + line: 0 + } + }; + } + + async createGroup(userId, { name, memberIds = [], announcement = '', welcomeText = '', channel = false }) { + const response = await axios.post(appConfig.conversationActionsApi, { + action: channel ? 'create_channel' : 'create_group', + userId, + name, + memberIds, + announcement, + welcomeText + }, { withCredentials: false }); + + if (response.data?.code !== 0) { + throw new Error(response.data?.message || (channel ? '创建公告群失败' : '创建群组失败')); + } + + return this.normalizeConversation(response.data?.result?.conversation || {}); + } + + async joinGroup(userId, groupKey) { + const response = await axios.post(appConfig.conversationActionsApi, { + action: 'join_group', + userId, + groupKey + }, { withCredentials: false }); + + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '加入群组失败'); + } + + return this.normalizeConversation(response.data?.result?.conversation || {}); + } + + async broadcastMessage(userId, targetIds, text) { + const response = await axios.post(appConfig.conversationActionsApi, { + action: 'broadcast', + userId, + targetIds, + text + }, { withCredentials: false }); + + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '群发失败'); + } + + return response.data?.result || {}; + } + + async getGroupDetail(userId, groupId) { + const response = await axios.post(appConfig.conversationActionsApi, { + action: 'group_detail', + userId, + groupId + }, { withCredentials: false }); + + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '群资料加载失败'); + } + + return response.data?.result?.group || null; + } + + async getGroupMembers(userId, groupId) { + const response = await axios.post(appConfig.conversationActionsApi, { + action: 'group_members', + userId, + groupId + }, { withCredentials: false }); + + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '群员加载失败'); + } + + return response.data?.result?.members || []; + } + + async updateGroupAlias(userId, groupId, alias) { + const response = await axios.post(appConfig.conversationActionsApi, { + action: 'update_group_alias', + userId, + groupId, + alias + }, { withCredentials: false }); + + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '本群昵称保存失败'); + } + + return response.data?.result?.alias || ''; + } + + async leaveGroup(userId, groupId) { + const response = await axios.post(appConfig.conversationActionsApi, { + action: 'leave_group', + userId, + groupId + }, { withCredentials: false }); + + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '退出群组失败'); + } + + return response.data?.result || {}; + } + + async dismissGroup(userId, groupId) { + const response = await axios.post(appConfig.conversationActionsApi, { + action: 'dismiss_group', + userId, + groupId + }, { withCredentials: false }); + + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '解散群组失败'); + } + + return response.data?.result || {}; + } + + async transferGroup(userId, groupId, targetUserId) { + const response = await axios.post(appConfig.conversationActionsApi, { + action: 'transfer_group', + userId, + groupId, + targetUserId + }, { withCredentials: false }); + + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '转让群组失败'); + } + + return response.data?.result || {}; + } + + async setGroupAdmins(userId, groupId, adminIds) { + const response = await axios.post(appConfig.conversationActionsApi, { + action: 'set_group_admins', + userId, + groupId, + adminIds + }, { withCredentials: false }); + + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '管理员设置失败'); + } + + return response.data?.result || {}; + } + + async updateGroupProfile(userId, groupId, { name, announcement, welcomeText }) { + const response = await axios.post(appConfig.conversationActionsApi, { + action: 'update_group_profile', + userId, + groupId, + name, + announcement, + welcomeText + }, { withCredentials: false }); + + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '群资料保存失败'); + } + + return response.data?.result || {}; + } + + async kickGroupMembers(userId, groupId, targetIds) { + const response = await axios.post(appConfig.conversationActionsApi, { + action: 'kick_group_members', + userId, + groupId, + targetIds + }, { withCredentials: false }); + + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '踢人失败'); + } + + return response.data?.result || {}; + } + + async updateGroupMuteSetting(userId, groupId, setting) { + const response = await axios.post(appConfig.conversationActionsApi, { + action: 'update_mute_setting', + userId, + groupId, + ...setting + }, { withCredentials: false }); + + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '禁言设置保存失败'); + } + + return response.data?.result?.muteSetting || null; + } + + async updateContactAlias(userId, targetId, alias) { + const response = await axios.post(appConfig.updateAliasApi, { + userId, + targetId, + alias + }, { + withCredentials: false, + headers: this.getAppAuthHeaders(appConfig.updateAliasApi) + }); + + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '备注名修改失败'); + } + + return response.data?.result?.contact?.alias || ''; + } + + async getProfile(userId) { + const response = await axios.post(appConfig.profileApi, { + action: 'get', + userId + }, { withCredentials: false }); + + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '用户资料加载失败'); + } + + return response.data?.result?.profile || null; + } + + async updateProfile(userId, { displayName, portraitFile }) { + const formData = new FormData(); + formData.append('action', 'update'); + formData.append('userId', userId); + formData.append('displayName', displayName); + if (portraitFile) { + formData.append('portraitFile', portraitFile); + } + + const response = await axios.post(appConfig.profileApi, formData, { + withCredentials: false, + headers: this.getAppAuthHeaders(appConfig.appServer) + }); + + if (response.data?.code !== 0) { + throw new Error(response.data?.message || '用户资料保存失败'); + } + + return response.data?.result?.profile || null; + } + + async changePassword({ oldPassword, newPassword }) { + const response = await axios.post(appConfig.changePasswordApi, { + oldPassword, + newPassword + }, { + withCredentials: false, + headers: this.getAppAuthHeaders(appConfig.changePasswordApi) + }); + + if (response.data?.code !== 0) { + throw new Error(response.data?.message || response.data?.msg || '密码修改失败'); + } + + return true; + } + + async resetPasswordWithCode({ mobile, resetCode, newPassword }) { + const response = await axios.post(appConfig.resetPasswordApi, { + mobile, + resetCode, + newPassword + }, { withCredentials: false }); + + if (response.data?.code !== 0) { + throw new Error(response.data?.message || response.data?.msg || '密码设置失败'); + } + + return true; + } + + refreshContactList() { + const contacts = this.getContactList(); + this.emit('contactsChanged', contacts); + return contacts; + } + + createSingleConversation(userId, userInfo = null) { + const title = getTitle(userInfo?.title || userInfo?.displayName || userInfo?.name, userId); + return { + id: `single-${userId}`, + type: 'single', + target: userId, + title, + subtitle: '', + unread: 0, + portrait: userInfo?.portrait || '', + avatarText: title.slice(0, 1).toUpperCase(), + updatedAt: Date.now(), + raw: { type: 'single', target: userId, line: 0 } + }; + } + + mapContact(userId, friend, userInfo) { + const title = getTitle(userInfo?.friendAlias || friend?.alias || userInfo?.displayName || userInfo?.name, userId); + return { + id: userId, + uid: userId, + title, + subtitle: userInfo?.mobile || userInfo?.name || userId, + portrait: userInfo?.portrait || '', + address: userInfo?.address || '', + avatarText: title.slice(0, 1).toUpperCase(), + alias: friend?.alias || userInfo?.friendAlias || '', + rawFriend: friend, + rawUser: userInfo + }; + } + + on(eventName, handler) { + if (!this.listeners.has(eventName)) { + this.listeners.set(eventName, new Set()); + } + this.listeners.get(eventName).add(handler); + return () => this.off(eventName, handler); + } + + off(eventName, handler) { + this.listeners.get(eventName)?.delete(handler); + } + + emit(eventName, payload) { + this.listeners.get(eventName)?.forEach(handler => handler(payload)); + } + + persistAppToken(url, response) { + const appToken = response.headers?.authtoken || response.headers?.authToken; + if (appToken) { + localStorage.setItem(appConfig.authTokenStoragePrefix + this.getUrlHost(url), appToken); + } + } + + getAppAuthHeaders(url) { + const host = this.getUrlHost(url); + if (!host) { + return {}; + } + const token = localStorage.getItem(appConfig.authTokenStoragePrefix + host); + return token ? { authToken: token } : {}; + } + + getUrlHost(url) { + try { + return new URL(url, window.location.origin).host; + } catch (error) { + return ''; + } + } +} + +export const imClient = new HttpImClient(); diff --git a/src/services/imClient.js b/src/services/imClient.js new file mode 100644 index 0000000..c2e6acb --- /dev/null +++ b/src/services/imClient.js @@ -0,0 +1,23 @@ +import { appConfig } from '@/config'; +import { HttpImClient } from './httpImClient'; +import { LongConnectionImClient } from './longConnectionImClient'; +import { DocumentedHttpImClient } from './documentedHttpImClient'; + +class UnsupportedWildfireImClient { + constructor() { + throw new Error('当前本地社区版 Web 客户端未启用 WildFire Web SDK'); + } +} + +export const imClient = appConfig.sdkMode === 'wildfire' + ? new UnsupportedWildfireImClient() + : appConfig.sdkMode === 'custom-ws' || appConfig.sdkMode === 'long' + ? new LongConnectionImClient() + : new DocumentedHttpImClient(); + +export { + DocumentedHttpImClient, + HttpImClient, + LongConnectionImClient, + UnsupportedWildfireImClient as WildfireImClient +}; diff --git a/src/services/longConnectionImClient.js b/src/services/longConnectionImClient.js new file mode 100644 index 0000000..d37e33d --- /dev/null +++ b/src/services/longConnectionImClient.js @@ -0,0 +1,236 @@ +import { appConfig } from '@/config'; +import { HttpImClient } from './httpImClient'; + +const HEARTBEAT_INTERVAL_MS = 25000; +const RECONNECT_MIN_MS = 1000; +const RECONNECT_MAX_MS = 15000; + +function buildWsUrl() { + if (appConfig.wsUrl) { + return appConfig.wsUrl; + } + const protocol = appConfig.useWss ? 'wss' : 'ws'; + const host = appConfig.imServerHost || window.location.hostname; + const port = appConfig.imRoutePort ? `:${appConfig.imRoutePort}` : ''; + const path = appConfig.wsPath?.startsWith('/') ? appConfig.wsPath : `/${appConfig.wsPath || 'ws'}`; + return `${protocol}://${host}${port}${path}`; +} + +function normalizeConversationId(payload) { + const target = payload.target || payload.to || payload.receiver || payload.conversationTarget || payload.from || payload.sender; + return payload.conversationId || (target ? `single-${target}` : ''); +} + +function normalizeIncomingMessage(payload, currentUserId) { + const item = payload.message || payload; + const from = item.from || item.sender || item.fromUser || ''; + const target = item.target || item.to || item.receiver || ''; + const direction = from && String(from) === String(currentUserId) ? 'out' : 'in'; + const conversationId = item.conversationId || normalizeConversationId({ + ...item, + target: direction === 'out' ? target : from + }); + + if (!conversationId) { + return null; + } + + const status = item.status || item.deliveryStatus || 'sent'; + + return { + id: item.id || item.messageId || item.messageUid || `${Date.now()}${Math.random().toString(16).slice(2)}`, + messageUid: item.messageUid || item.uid || item.messageId || '', + conversationId, + from, + target, + direction, + type: item.type || 'text', + text: item.text || item.content || item.searchableContent || '', + status, + deliveryStatus: item.deliveryStatus || (direction === 'out' ? status : ''), + timestamp: Number(item.timestamp || item.serverTimestamp || Date.now()), + displayTimestamp: Number(item.displayTimestamp || item.timestamp || item.serverTimestamp || Date.now()), + raw: item + }; +} + +export class LongConnectionImClient extends HttpImClient { + constructor() { + super(); + this.socket = null; + this.wsUrl = buildWsUrl(); + this.heartbeatTimer = null; + this.reconnectTimer = null; + this.reconnectAttempts = 0; + this.manualClose = false; + } + + getSdkVersion() { + return 'JC-LC/0.1'; + } + + shouldUseMessagePolling() { + return !this.connected; + } + + async ensureSdk() { + if (typeof WebSocket === 'undefined') { + throw new Error('当前浏览器不支持 WebSocket 长连接'); + } + return true; + } + + connect(userId, token) { + this.currentUser = { userId, token }; + this.manualClose = false; + this.openSocket(); + } + + disconnect() { + this.manualClose = true; + this.clearReconnectTimer(); + this.clearHeartbeatTimer(); + if (this.socket) { + this.socket.close(); + this.socket = null; + } + super.disconnect(); + } + + openSocket() { + if (this.socket?.readyState === WebSocket.OPEN) { + this.sendAuthFrame(); + return; + } + if (this.socket?.readyState === WebSocket.CONNECTING) { + return; + } + + this.emit('connectionStatusChanged', { connected: false, statusText: '长连接连接中' }); + + try { + this.socket = new WebSocket(this.wsUrl); + this.socket.binaryType = 'arraybuffer'; + } catch (error) { + this.emit('connectionStatusChanged', { connected: false, statusText: '长连接创建失败,使用HTTP补偿' }); + this.scheduleReconnect(); + return; + } + + this.socket.onopen = () => { + this.connected = true; + this.reconnectAttempts = 0; + this.emit('connectionStatusChanged', { connected: true, statusText: '长连接已连接' }); + this.sendAuthFrame(); + this.startHeartbeat(); + this.refreshConversationList(); + }; + + this.socket.onmessage = event => { + this.handleFrame(event.data); + }; + + this.socket.onerror = () => { + this.emit('connectionStatusChanged', { connected: false, statusText: '长连接异常,使用HTTP补偿' }); + }; + + this.socket.onclose = () => { + this.connected = false; + this.clearHeartbeatTimer(); + this.socket = null; + if (this.manualClose) { + return; + } + this.emit('connectionStatusChanged', { connected: false, statusText: '长连接重连中' }); + this.scheduleReconnect(); + }; + } + + sendAuthFrame() { + this.sendFrame({ + type: 'auth', + userId: this.currentUser?.userId || '', + token: this.currentUser?.token || '', + clientId: this.getClientId(), + platform: appConfig.platform, + timestamp: Date.now() + }); + } + + sendFrame(frame) { + if (this.socket?.readyState !== WebSocket.OPEN) { + return false; + } + this.socket.send(JSON.stringify(frame)); + return true; + } + + handleFrame(rawData) { + if (rawData instanceof ArrayBuffer) { + this.emit('rawFrameReceived', rawData); + return; + } + + let payload; + try { + payload = typeof rawData === 'string' ? JSON.parse(rawData) : rawData; + } catch (error) { + return; + } + + const type = payload.type || payload.event || payload.cmd || ''; + if (type === 'pong' || type === 'auth_ok' || type === 'connected') { + return; + } + if (type === 'contactsChanged') { + this.emit('contactsChanged', payload.contacts || []); + return; + } + if (type === 'conversationsChanged') { + this.emit('conversationsChanged', payload.conversations || []); + return; + } + if (type === 'messages' && Array.isArray(payload.messages)) { + payload.messages + .map(message => normalizeIncomingMessage(message, this.currentUser?.userId)) + .filter(Boolean) + .forEach(message => this.emit('messageReceived', message)); + return; + } + + const message = normalizeIncomingMessage(payload, this.currentUser?.userId); + if (message) { + this.emit(message.direction === 'out' ? 'messageStatusChanged' : 'messageReceived', message); + } + } + + startHeartbeat() { + this.clearHeartbeatTimer(); + this.heartbeatTimer = window.setInterval(() => { + this.sendFrame({ type: 'ping', timestamp: Date.now() }); + }, HEARTBEAT_INTERVAL_MS); + } + + clearHeartbeatTimer() { + if (this.heartbeatTimer) { + window.clearInterval(this.heartbeatTimer); + this.heartbeatTimer = null; + } + } + + scheduleReconnect() { + this.clearReconnectTimer(); + const delay = Math.min(RECONNECT_MAX_MS, RECONNECT_MIN_MS * 2 ** this.reconnectAttempts); + this.reconnectAttempts += 1; + this.reconnectTimer = window.setTimeout(() => { + this.openSocket(); + }, delay); + } + + clearReconnectTimer() { + if (this.reconnectTimer) { + window.clearTimeout(this.reconnectTimer); + this.reconnectTimer = null; + } + } +} diff --git a/src/stores/contacts.js b/src/stores/contacts.js new file mode 100644 index 0000000..1195693 --- /dev/null +++ b/src/stores/contacts.js @@ -0,0 +1,233 @@ +import { defineStore } from 'pinia'; +import { imClient } from '@/services/imClient'; +import { useConversationStore } from '@/stores/conversations'; +import { useNavigationStore } from '@/stores/navigation'; +import { useSessionStore } from '@/stores/session'; + +const CONTACT_CACHE_MS = 15000; + +function normalizeContacts(contacts) { + const unique = new Map(); + contacts.forEach(contact => { + const uid = String(contact.uid || contact.id || '').trim(); + if (!uid) { + return; + } + const normalized = { + ...contact, + id: String(contact.id || uid), + uid + }; + unique.set(uid, { + ...(unique.get(uid) || {}), + ...normalized + }); + }); + return Array.from(unique.values()).sort((a, b) => a.title.localeCompare(b.title, 'zh-CN')); +} + +export const useContactStore = defineStore('contacts', { + state: () => ({ + contacts: [], + activeId: '', + loading: false, + eventsBound: false, + refreshSerial: 0, + refreshPromise: null, + loadedForUserId: '', + loadedAt: 0, + actionError: '', + actionInfo: '' + }), + getters: { + activeContact(state) { + return state.contacts.find(item => item.id === state.activeId) || state.contacts[0] || null; + } + }, + actions: { + bindImEvents() { + if (this.eventsBound) { + return; + } + this.eventsBound = true; + imClient.on('contactsChanged', contacts => { + if (!contacts.length && this.contacts.length) { + return; + } + this.contacts = normalizeContacts(contacts); + if (!this.contacts.find(item => item.id === this.activeId)) { + this.activeId = this.contacts[0]?.id || ''; + } + }); + }, + async refreshContacts(options = {}) { + this.bindImEvents(); + const session = useSessionStore(); + const requestUserId = session.userId; + const force = Boolean(options.force); + if (!requestUserId) { + this.clearContacts(); + return []; + } + if (!force + && this.contacts.length + && this.loadedForUserId === requestUserId + && Date.now() - this.loadedAt < CONTACT_CACHE_MS + ) { + return this.contacts; + } + if (this.refreshPromise && !force) { + return this.refreshPromise; + } + const serial = this.refreshSerial + 1; + this.refreshSerial = serial; + this.loading = true; + + this.refreshPromise = (async () => { + this.contacts = normalizeContacts(imClient.refreshContactList()); + if (!this.contacts.find(item => item.id === this.activeId)) { + this.activeId = this.contacts[0]?.id || ''; + } + if (!this.contacts.length || force) { + const serverContacts = await imClient.getServerContactList(requestUserId); + if (serial !== this.refreshSerial || useSessionStore().userId !== requestUserId) { + return this.contacts; + } + this.contacts = normalizeContacts(serverContacts); + this.loadedForUserId = requestUserId; + this.loadedAt = Date.now(); + if (!this.contacts.find(item => item.id === this.activeId)) { + this.activeId = this.contacts[0]?.id || ''; + } + } + window.setTimeout(() => { + if (serial !== this.refreshSerial || useSessionStore().userId !== requestUserId) { + return; + } + const latest = imClient.getContactList(false); + if (latest.length || !this.contacts.length) { + this.contacts = normalizeContacts(latest); + this.loadedForUserId = requestUserId; + this.loadedAt = Date.now(); + if (!this.contacts.find(item => item.id === this.activeId)) { + this.activeId = this.contacts[0]?.id || ''; + } + } + }, 1200); + return this.contacts; + })(); + + try { + return await this.refreshPromise; + } finally { + if (serial === this.refreshSerial) { + this.loading = false; + this.refreshPromise = null; + } + } + }, + async addContact(keyword) { + const session = useSessionStore(); + const value = String(keyword || '').trim(); + this.actionError = ''; + this.actionInfo = ''; + if (!session.userId) { + this.actionError = '请先登录'; + return false; + } + if (!value) { + this.actionError = '请输入用户ID、手机号或用户名'; + return false; + } + + this.loading = true; + try { + const contact = await imClient.addContact(session.userId, value, '添加联系人'); + if (contact) { + this.contacts = normalizeContacts([...this.contacts, contact]); + this.activeId = contact.id; + } + this.actionInfo = '联系人已添加'; + await this.refreshContacts({ force: true }); + return true; + } catch (error) { + this.actionError = error.message || '添加联系人失败'; + return false; + } finally { + this.loading = false; + } + }, + async updateContactAlias(contactId, alias) { + const session = useSessionStore(); + const targetId = String(contactId || '').trim(); + const nextAlias = String(alias || '').trim(); + this.actionError = ''; + this.actionInfo = ''; + if (!session.userId) { + this.actionError = '请先登录'; + return false; + } + if (!targetId) { + this.actionError = '缺少联系人ID'; + return false; + } + + try { + const savedAlias = await imClient.updateContactAlias(session.userId, targetId, nextAlias); + this.contacts = normalizeContacts(this.contacts.map(item => { + if (item.uid !== targetId && item.id !== targetId) { + return item; + } + const rawUser = { + ...(item.rawUser || {}), + friendAlias: savedAlias + }; + const rawFriend = { + ...(item.rawFriend || {}), + alias: savedAlias + }; + const title = savedAlias || rawUser.displayName || rawUser.name || item.uid; + return { + ...item, + title, + avatarText: title.slice(0, 1).toUpperCase(), + alias: savedAlias, + rawFriend, + rawUser + }; + })); + this.loadedAt = Date.now(); + this.actionInfo = '备注名已修改'; + return true; + } catch (error) { + this.actionError = error.message || '备注名修改失败'; + return false; + } + }, + clearContacts() { + this.refreshSerial += 1; + this.contacts = []; + this.activeId = ''; + this.loading = false; + this.refreshPromise = null; + this.loadedForUserId = ''; + this.loadedAt = 0; + this.actionError = ''; + this.actionInfo = ''; + }, + setActiveContact(id) { + this.activeId = id; + }, + openChat(contact) { + if (!contact) { + return; + } + const conversations = useConversationStore(); + const navigation = useNavigationStore(); + const conversation = imClient.createSingleConversation(contact.uid, contact); + conversations.upsertConversation(conversation); + conversations.setActiveConversation(conversation.id); + navigation.showMessages(); + } + } +}); diff --git a/src/stores/conversations.js b/src/stores/conversations.js new file mode 100644 index 0000000..32e8919 --- /dev/null +++ b/src/stores/conversations.js @@ -0,0 +1,1216 @@ +import { defineStore } from 'pinia'; +import { imClient } from '@/services/imClient'; +import { appConfig } from '@/config'; +import { useSessionStore } from '@/stores/session'; + +const LOCAL_CONVERSATION_STORAGE_KEY = 'qmt-web-im-local-conversations-v2'; +const LOCAL_FAVORITE_MESSAGES_KEY = 'qmt-web-im-favorite-messages-v1'; + +function scopedStorageKey(baseKey) { + const session = useSessionStore(); + const scope = `${appConfig.appServer || ''}:${session.userId || 'anonymous'}`; + return `${baseKey}:${encodeURIComponent(scope)}`; +} + +function readFavoriteMessages() { + try { + return JSON.parse(localStorage.getItem(scopedStorageKey(LOCAL_FAVORITE_MESSAGES_KEY)) || '[]'); + } catch (error) { + return []; + } +} + +function writeFavoriteMessages(messages) { + localStorage.setItem(scopedStorageKey(LOCAL_FAVORITE_MESSAGES_KEY), JSON.stringify(messages)); +} + +function readLocalConversations() { + try { + return JSON.parse(localStorage.getItem(scopedStorageKey(LOCAL_CONVERSATION_STORAGE_KEY)) || '[]'); + } catch (error) { + return []; + } +} + +function writeLocalConversations(conversations) { + localStorage.setItem(scopedStorageKey(LOCAL_CONVERSATION_STORAGE_KEY), JSON.stringify(conversations)); +} + +function randomId() { + if (window.crypto && window.crypto.randomUUID) { + return window.crypto.randomUUID().replaceAll('-', ''); + } + return `${Date.now()}${Math.random().toString(16).slice(2)}`; +} + +function messageKey(message) { + return message.id || message.messageUid; +} + +function messageStableKey(message) { + return String(message?.messageUid || message?.id || ''); +} + +function messageActivityTimestamp(message) { + return Number(message?.activityTimestamp || message?.editedAt || message?.displayTimestamp || message?.timestamp || 0); +} + +function isRecalledMessage(message) { + return Boolean(message?.recalled) || message?.status === 'recalled'; +} + +function attachmentLabel(message) { + const attachment = message?.attachment; + if (!attachment) { + return ''; + } + if (attachment.type === 'image') { + return '[图片]'; + } + return `[文件] ${attachment.name || '文件'}`; +} + +function messagePreviewText(message) { + if (isRecalledMessage(message)) { + return `[${message?.recallStatus || '已撤回'}]`; + } + const text = String(message?.text || '').replace(/\s+/g, ' ').trim(); + return text || attachmentLabel(message); +} + +function quotePrefix(message) { + const text = messagePreviewText(message); + return text.length > 48 ? `${text.slice(0, 48)}...` : text; +} + +async function writeClipboard(text) { + if (navigator.clipboard?.writeText) { + await navigator.clipboard.writeText(text); + return; + } + const element = document.createElement('textarea'); + element.value = text; + element.setAttribute('readonly', 'readonly'); + element.style.position = 'fixed'; + element.style.left = '-9999px'; + document.body.appendChild(element); + element.select(); + document.execCommand('copy'); + document.body.removeChild(element); +} + +function isSameMessage(left, right) { + if (left.messageUid && right.messageUid && left.messageUid === right.messageUid) { + return true; + } + if (left.id && right.id && left.id === right.id) { + return true; + } + return left.direction === right.direction + && left.text === right.text + && (left.type || 'text') === (right.type || 'text') + && Math.abs(Number(left.displayTimestamp || left.timestamp || 0) - Number(right.displayTimestamp || right.timestamp || 0)) < 15000; +} + +function hasExactMessage(messages, message) { + return messages.some(item => { + if (item.id && message.id && item.id === message.id) { + return true; + } + return item.messageUid && message.messageUid && item.messageUid === message.messageUid; + }); +} + +function shouldUseMessagePolling() { + return imClient.shouldUseMessagePolling ? imClient.shouldUseMessagePolling() : true; +} + +function canMarkReadNow(options = {}) { + if (typeof document === 'undefined') { + return false; + } + if (document.visibilityState !== 'visible') { + return false; + } + if (options.userAction) { + return true; + } + return true; +} + +function mergeVisibleMessages(currentMessages, syncedMessages) { + const merged = [...syncedMessages]; + currentMessages.forEach(currentMessage => { + const exists = merged.some(message => isSameMessage(message, currentMessage)); + const shouldKeepLocal = currentMessage.status === 'sending' + || currentMessage.status === 'failed' + || (currentMessage.direction === 'out' + && currentMessage.status === 'sent' + && Date.now() - Number(currentMessage.timestamp || 0) < 30000); + if (!exists && shouldKeepLocal) { + merged.push(currentMessage); + } + }); + + return merged + .filter((message, index, messages) => { + const key = messageKey(message); + return messages.findIndex(item => messageKey(item) === key) === index; + }) + .sort((a, b) => Number(a.displayTimestamp || a.timestamp || 0) - Number(b.displayTimestamp || b.timestamp || 0)); +} + +function incomingEditedMessages(previousMessages, nextMessages) { + const previousByKey = new Map(); + (previousMessages || []).forEach(message => { + const key = messageStableKey(message); + if (key) { + previousByKey.set(key, message); + } + }); + + return (nextMessages || []).filter(message => { + if (message.direction !== 'in') { + return false; + } + const editedAt = Number(message.editedAt || 0); + if (editedAt <= 0) { + return false; + } + const previous = previousByKey.get(messageStableKey(message)); + return previous && editedAt > Number(previous.editedAt || 0); + }); +} + +let notificationAudioContext = null; + +function playNotificationSound() { + if (typeof window === 'undefined') { + return; + } + try { + const AudioContext = window.AudioContext || window.webkitAudioContext; + if (!AudioContext) { + return; + } + notificationAudioContext = notificationAudioContext || new AudioContext(); + if (notificationAudioContext.state === 'suspended') { + notificationAudioContext.resume().catch(() => {}); + } + const oscillator = notificationAudioContext.createOscillator(); + const gain = notificationAudioContext.createGain(); + const startAt = notificationAudioContext.currentTime; + oscillator.type = 'sine'; + oscillator.frequency.setValueAtTime(880, startAt); + oscillator.frequency.setValueAtTime(660, startAt + 0.1); + gain.gain.setValueAtTime(0.0001, startAt); + gain.gain.exponentialRampToValueAtTime(0.12, startAt + 0.02); + gain.gain.exponentialRampToValueAtTime(0.0001, startAt + 0.22); + oscillator.connect(gain); + gain.connect(notificationAudioContext.destination); + oscillator.start(startAt); + oscillator.stop(startAt + 0.24); + } catch (error) { + // Notification sound is best-effort; browsers may block audio until user interaction. + } +} + +export const useConversationStore = defineStore('conversations', { + state: () => ({ + activeId: '', + conversations: [], + messagesByConversation: {}, + visibleMessagesByConversation: {}, + eventsBound: false, + usingDemoData: false, + loading: false, + syncTimer: null, + conversationSyncTimer: null, + readPresenceEventsBound: false, + syncInFlight: false, + pendingReadReceiptConversationId: '', + conversationSyncInFlight: false, + syncAfterSendTimer: null, + optimisticMessagesByConversation: {}, + quotedMessage: null, + forwardingMessages: [], + editingMessage: null, + editingText: '', + editingMode: '', + messageDetailOpen: false, + messageDetailLoading: false, + messageDetailError: '', + messageDetail: null, + attentionMessageId: '', + attentionMessageTimer: null, + selectionMode: false, + selectedMessageIds: [], + favoriteMessages: [], + activeFavoriteKey: '', + actionNotice: '', + actionError: '' + }), + getters: { + activeConversation(state) { + return state.conversations.find(item => item.id === state.activeId) || state.conversations[0] || null; + }, + activeMessages(state) { + return state.visibleMessagesByConversation[state.activeId] || []; + }, + totalUnread(state) { + return state.conversations.reduce((sum, item) => sum + Number(item.unread || 0), 0); + }, + canSendActiveConversation(state) { + const conversation = state.conversations.find(item => item.id === state.activeId) || state.conversations[0] || null; + if (!conversation) { + return false; + } + if (conversation.type !== 'channel') { + return true; + } + return ['owner', 'admin'].includes(conversation.role || conversation.raw?.role || ''); + }, + favoriteMessageIds(state) { + return state.favoriteMessages.map(message => message.key); + }, + activeFavorite(state) { + return state.favoriteMessages.find(message => message.key === state.activeFavoriteKey) + || state.favoriteMessages[0] + || null; + }, + selectedMessages(state) { + const selectedIds = new Set(state.selectedMessageIds); + return (state.visibleMessagesByConversation[state.activeId] || []) + .filter(message => selectedIds.has(messageStableKey(message))); + } + }, + actions: { + bootstrapDemoData() { + const session = useSessionStore(); + this.bindImEvents(); + this.usingDemoData = false; + if (!session.userId) { + this.clearUserState(); + return; + } + this.loadScopedLocalState(); + this.startMessageSync(); + }, + loadScopedLocalState() { + this.conversations = []; + this.favoriteMessages = readFavoriteMessages(); + this.activeFavoriteKey = this.favoriteMessages[0]?.key || ''; + this.activeId = ''; + this.messagesByConversation = {}; + this.visibleMessagesByConversation = {}; + this.optimisticMessagesByConversation = {}; + this.quotedMessage = null; + this.forwardingMessages = []; + this.editingMessage = null; + this.editingText = ''; + this.editingMode = ''; + this.messageDetailOpen = false; + this.messageDetailLoading = false; + this.messageDetailError = ''; + this.messageDetail = null; + this.selectionMode = false; + this.selectedMessageIds = []; + }, + clearUserState() { + window.clearInterval(this.syncTimer); + window.clearInterval(this.conversationSyncTimer); + window.clearTimeout(this.syncAfterSendTimer); + window.clearTimeout(this.attentionMessageTimer); + this.syncTimer = null; + this.conversationSyncTimer = null; + this.syncAfterSendTimer = null; + this.attentionMessageTimer = null; + this.syncInFlight = false; + this.conversationSyncInFlight = false; + this.pendingReadReceiptConversationId = ''; + this.activeId = ''; + this.conversations = []; + this.messagesByConversation = {}; + this.visibleMessagesByConversation = {}; + this.optimisticMessagesByConversation = {}; + this.quotedMessage = null; + this.forwardingMessages = []; + this.editingMessage = null; + this.editingText = ''; + this.editingMode = ''; + this.messageDetailOpen = false; + this.messageDetailLoading = false; + this.messageDetailError = ''; + this.messageDetail = null; + this.attentionMessageId = ''; + this.selectionMode = false; + this.selectedMessageIds = []; + this.favoriteMessages = []; + this.activeFavoriteKey = ''; + this.actionNotice = ''; + this.actionError = ''; + }, + bindImEvents() { + if (this.eventsBound) { + return; + } + this.eventsBound = true; + imClient.on('messageSent', message => { + this.appendMessage(message.conversationId, message); + }); + imClient.on('messageReceived', message => { + this.appendMessage(message.conversationId, message); + if (message.conversationId === this.activeId) { + this.syncActiveMessages({ allowReadReceipt: true }); + } + }); + imClient.on('messageStatusChanged', message => { + this.replaceMessage(message.conversationId, message); + }); + imClient.on('conversationsChanged', remoteConversations => { + this.usingDemoData = false; + this.applyRemoteConversations(remoteConversations); + }); + }, + setActiveConversation(id) { + this.activeId = id; + const conversation = this.conversations.find(item => item.id === id); + if (conversation) { + conversation.unread = 0; + } + this.loadActiveMessages({ allowReadReceipt: true, userAction: true }); + }, + sendText(text) { + const conversation = this.activeConversation; + if (!conversation || !text.trim()) { + return; + } + let value = text.trim(); + if (this.quotedMessage) { + value = `引用:${quotePrefix(this.quotedMessage)}\n${value}`; + this.quotedMessage = null; + } + const message = { + id: randomId(), + conversationId: conversation.id, + from: 'me', + direction: 'out', + type: 'text', + text: value, + status: 'sending', + deliveryStatus: 'sending', + timestamp: Date.now(), + displayTimestamp: Date.now() + }; + this.appendVisibleMessage(conversation.id, message); + this.appendMessage(conversation.id, message); + imClient.sendTextMessage(conversation, value, message); + conversation.subtitle = value; + conversation.updatedAt = Date.now(); + writeLocalConversations(this.conversations); + window.clearTimeout(this.syncAfterSendTimer); + this.syncAfterSendTimer = window.setTimeout(() => this.syncActiveMessages(), 250); + }, + sendAttachment({ type, file, text = '' }) { + const conversation = this.activeConversation; + if (!conversation || !file || !['image', 'file'].includes(type)) { + return; + } + let value = text.trim(); + if (this.quotedMessage) { + value = `引用:${quotePrefix(this.quotedMessage)}\n${value}`.trim(); + this.quotedMessage = null; + } + const now = Date.now(); + const message = { + id: randomId(), + conversationId: conversation.id, + from: 'me', + senderRole: conversation.role || conversation.raw?.role || '', + direction: 'out', + type, + text: value, + attachment: { + type, + name: file.name || (type === 'image' ? '图片' : '文件'), + mime: file.type || '', + size: file.size || 0, + url: type === 'image' ? URL.createObjectURL(file) : '' + }, + status: 'sending', + deliveryStatus: 'sending', + timestamp: now, + displayTimestamp: now + }; + this.appendVisibleMessage(conversation.id, message); + this.appendMessage(conversation.id, message); + imClient.sendAttachmentMessage(conversation, { type, file, text: value }, message); + conversation.subtitle = messagePreviewText(message); + conversation.updatedAt = now; + writeLocalConversations(this.conversations); + window.clearTimeout(this.syncAfterSendTimer); + this.syncAfterSendTimer = window.setTimeout(() => this.syncActiveMessages(), 350); + }, + async sendTextToConversation(conversation, text) { + if (!conversation || !text.trim()) { + return null; + } + const value = text.trim(); + const message = { + id: randomId(), + conversationId: conversation.id, + from: 'me', + direction: 'out', + type: 'text', + text: value, + status: 'sending', + deliveryStatus: 'sending', + timestamp: Date.now(), + displayTimestamp: Date.now() + }; + this.appendMessage(conversation.id, message); + const sentMessage = await imClient.sendTextMessage(conversation, value, message); + this.replaceMessage(conversation.id, sentMessage); + return sentMessage; + }, + upsertConversation(conversation) { + const index = this.conversations.findIndex(item => item.id === conversation.id); + if (index >= 0) { + this.conversations.splice(index, 1, { + ...this.conversations[index], + ...conversation + }); + } else { + this.conversations.unshift(conversation); + } + this.usingDemoData = false; + writeLocalConversations(this.conversations); + }, + removeConversation(conversationId) { + this.conversations = this.conversations.filter(conversation => conversation.id !== conversationId); + const { + [conversationId]: removedMessages, + ...messagesByConversation + } = this.messagesByConversation; + const { + [conversationId]: removedVisibleMessages, + ...visibleMessagesByConversation + } = this.visibleMessagesByConversation; + const { + [conversationId]: removedOptimisticMessages, + ...optimisticMessagesByConversation + } = this.optimisticMessagesByConversation; + void removedMessages; + void removedVisibleMessages; + void removedOptimisticMessages; + this.messagesByConversation = messagesByConversation; + this.visibleMessagesByConversation = visibleMessagesByConversation; + this.optimisticMessagesByConversation = optimisticMessagesByConversation; + if (this.activeId === conversationId) { + this.activeId = this.conversations[0]?.id || ''; + } + writeLocalConversations(this.conversations); + if (this.activeId) { + this.loadActiveMessages(); + } + }, + applyRemoteConversations(remoteConversations) { + const remote = Array.isArray(remoteConversations) ? remoteConversations : []; + if (!remote.length && this.conversations.length) { + return; + } + + const merged = new Map(); + this.conversations.forEach(conversation => { + merged.set(conversation.id, conversation); + }); + remote.forEach(conversation => { + const unread = conversation.id === this.activeId && canMarkReadNow() ? 0 : Number(conversation.unread || 0); + merged.set(conversation.id, { + ...(merged.get(conversation.id) || {}), + ...conversation, + unread + }); + }); + + this.conversations = Array.from(merged.values()) + .sort((a, b) => Number(b.updatedAt || 0) - Number(a.updatedAt || 0)) + .slice(0, 100); + + if (!this.conversations.length) { + this.activeId = ''; + this.messagesByConversation = {}; + this.visibleMessagesByConversation = {}; + this.optimisticMessagesByConversation = {}; + writeLocalConversations(this.conversations); + return; + } + + if (!this.conversations.find(item => item.id === this.activeId)) { + this.activeId = this.conversations[0].id; + } + writeLocalConversations(this.conversations); + this.loadActiveMessages(); + }, + appendMessage(conversationId, message) { + if (!this.messagesByConversation[conversationId]) { + this.messagesByConversation = { + ...this.messagesByConversation, + [conversationId]: [] + }; + } + if (message.direction === 'out' && (message.status === 'sending' || message.status === 'sent')) { + this.keepOptimisticMessage(conversationId, message); + } + if (!hasExactMessage(this.messagesByConversation[conversationId], message)) { + this.messagesByConversation = { + ...this.messagesByConversation, + [conversationId]: [...this.messagesByConversation[conversationId], message] + }; + } + this.updateConversationFromMessage(conversationId, message); + this.syncVisibleMessages(conversationId); + writeLocalConversations(this.conversations); + }, + removeMessages(conversationId, messages) { + const ids = new Set(messages.flatMap(message => [message.id, message.messageUid].filter(Boolean).map(String))); + const filterMessages = list => (list || []).filter(message => { + return ![message.id, message.messageUid].filter(Boolean).some(id => ids.has(String(id))); + }); + this.messagesByConversation = { + ...this.messagesByConversation, + [conversationId]: filterMessages(this.messagesByConversation[conversationId]) + }; + this.visibleMessagesByConversation = { + ...this.visibleMessagesByConversation, + [conversationId]: filterMessages(this.visibleMessagesByConversation[conversationId]) + }; + this.optimisticMessagesByConversation = { + ...this.optimisticMessagesByConversation, + [conversationId]: filterMessages(this.optimisticMessagesByConversation[conversationId]) + }; + this.selectedMessageIds = this.selectedMessageIds.filter(id => !ids.has(String(id))); + }, + replaceLocalMessage(conversationId, targetMessage, nextMessage) { + const keys = new Set([targetMessage.id, targetMessage.messageUid].filter(Boolean).map(String)); + const replaceInList = list => (list || []).map(message => { + const matched = [message.id, message.messageUid].filter(Boolean).some(id => keys.has(String(id))); + return matched ? { ...message, ...nextMessage } : message; + }); + this.messagesByConversation = { + ...this.messagesByConversation, + [conversationId]: replaceInList(this.messagesByConversation[conversationId]) + }; + this.visibleMessagesByConversation = { + ...this.visibleMessagesByConversation, + [conversationId]: replaceInList(this.visibleMessagesByConversation[conversationId]) + }; + }, + updateConversationFromMessage(conversationId, message) { + const conversation = this.conversations.find(item => item.id === conversationId); + if (!conversation) { + return; + } + + conversation.subtitle = message.text || conversation.subtitle || ''; + conversation.updatedAt = messageActivityTimestamp(message) || Date.now(); + if (message.direction === 'in') { + conversation.unread = conversationId === this.activeId ? 0 : Number(conversation.unread || 0) + 1; + } + this.conversations = [...this.conversations] + .sort((a, b) => Number(b.updatedAt || 0) - Number(a.updatedAt || 0)); + }, + appendVisibleMessage(conversationId, message) { + const messages = this.visibleMessagesByConversation[conversationId] || []; + this.visibleMessagesByConversation = { + ...this.visibleMessagesByConversation, + [conversationId]: [...messages.filter(item => item.id !== message.id), message] + .sort((a, b) => Number(a.displayTimestamp || a.timestamp || 0) - Number(b.displayTimestamp || b.timestamp || 0)) + }; + }, + replaceMessage(conversationId, message) { + if (!this.messagesByConversation[conversationId]) { + this.messagesByConversation = { + ...this.messagesByConversation, + [conversationId]: [] + }; + } + if (message.direction === 'out' && (message.status === 'sending' || message.status === 'sent' || message.status === 'failed')) { + this.keepOptimisticMessage(conversationId, message); + } + const index = this.messagesByConversation[conversationId].findIndex(item => item.id === message.id); + if (index >= 0) { + const messages = [...this.messagesByConversation[conversationId]]; + messages.splice(index, 1, message); + this.messagesByConversation = { + ...this.messagesByConversation, + [conversationId]: messages + }; + } else { + this.messagesByConversation = { + ...this.messagesByConversation, + [conversationId]: [...this.messagesByConversation[conversationId], message] + }; + } + this.syncVisibleMessages(conversationId); + }, + syncVisibleMessages(conversationId) { + this.visibleMessagesByConversation = { + ...this.visibleMessagesByConversation, + [conversationId]: mergeVisibleMessages( + this.optimisticMessagesByConversation[conversationId] || [], + this.messagesByConversation[conversationId] || [] + ) + }; + }, + keepOptimisticMessage(conversationId, message) { + if (!this.optimisticMessagesByConversation[conversationId]) { + this.optimisticMessagesByConversation = { + ...this.optimisticMessagesByConversation, + [conversationId]: [] + }; + } + const messages = this.optimisticMessagesByConversation[conversationId]; + const index = messages.findIndex(item => item.id === message.id); + let nextMessages; + if (index >= 0) { + nextMessages = [...messages]; + nextMessages.splice(index, 1, message); + } else { + nextMessages = [...messages, message]; + } + this.optimisticMessagesByConversation = { + ...this.optimisticMessagesByConversation, + [conversationId]: nextMessages + }; + }, + pruneOptimisticMessages(conversationId, syncedMessages) { + const messages = this.optimisticMessagesByConversation[conversationId] || []; + if (!messages.length) { + return; + } + this.optimisticMessagesByConversation = { + ...this.optimisticMessagesByConversation, + [conversationId]: messages.filter(message => { + const remoteMatched = syncedMessages.some(syncedMessage => { + return syncedMessage.id !== message.id && isSameMessage(syncedMessage, message); + }); + if (remoteMatched && message.status !== 'failed') { + return false; + } + return Date.now() - Number(message.displayTimestamp || message.timestamp || 0) < 30000 || message.status === 'failed'; + }) + }; + }, + loadActiveMessages(options = {}) { + const syncOptions = typeof options === 'boolean' + ? { allowReadReceipt: options } + : options; + const conversation = this.activeConversation; + if (!conversation) { + return; + } + this.loading = true; + const messages = imClient.getMessages(conversation, 30); + this.pruneOptimisticMessages(conversation.id, messages); + this.messagesByConversation = { + ...this.messagesByConversation, + [conversation.id]: mergeVisibleMessages(this.messagesByConversation[conversation.id] || [], messages) + }; + this.syncVisibleMessages(conversation.id); + this.loading = false; + this.syncActiveMessages(syncOptions); + }, + async syncActiveMessages(options = {}) { + const conversation = this.activeConversation; + if (!conversation) { + return; + } + const wantsReadReceipt = Boolean(options.allowReadReceipt); + const explicitReadReceipt = wantsReadReceipt && canMarkReadNow({ userAction: Boolean(options.userAction) }); + const pollingReadReceipt = canMarkReadNow(); + const markRead = explicitReadReceipt || pollingReadReceipt; + + if (this.syncInFlight) { + if (wantsReadReceipt) { + this.pendingReadReceiptConversationId = conversation.id; + } + return; + } + this.syncInFlight = true; + const syncedConversationId = conversation.id; + const previousMessages = this.messagesByConversation[syncedConversationId] || []; + try { + const messages = await imClient.fetchRemoteMessages(conversation, 50, { + markRead + }); + const editedIncomingMessages = incomingEditedMessages(previousMessages, messages); + this.pruneOptimisticMessages(syncedConversationId, messages); + this.messagesByConversation = { + ...this.messagesByConversation, + [syncedConversationId]: mergeVisibleMessages(this.messagesByConversation[syncedConversationId] || [], messages) + }; + this.syncVisibleMessages(syncedConversationId); + if (editedIncomingMessages.length && syncedConversationId === this.activeId) { + this.focusMessage(editedIncomingMessages.sort((a, b) => messageActivityTimestamp(b) - messageActivityTimestamp(a))[0]); + playNotificationSound(); + } + const latest = messages.reduce((latestMessage, message) => { + if (!latestMessage || messageActivityTimestamp(message) > messageActivityTimestamp(latestMessage)) { + return message; + } + return latestMessage; + }, null); + if (latest) { + conversation.subtitle = messagePreviewText(latest); + conversation.updatedAt = messageActivityTimestamp(latest); + writeLocalConversations(this.conversations); + } + } catch (error) { + // Keep the local view usable if a polling request misses. + } finally { + this.syncInFlight = false; + if (this.pendingReadReceiptConversationId === this.activeId && canMarkReadNow()) { + this.pendingReadReceiptConversationId = ''; + window.setTimeout(() => this.syncActiveMessages({ allowReadReceipt: true }), 0); + } + } + }, + startMessageSync() { + this.bindReadPresenceEvents(); + if (!this.syncTimer) { + this.syncTimer = window.setInterval(() => { + this.syncActiveMessages(); + }, Math.max(250, appConfig.syncIntervalMs)); + } + if (!this.conversationSyncTimer) { + this.conversationSyncTimer = window.setInterval(() => { + this.refreshRemoteConversations(); + }, 2000); + this.refreshRemoteConversations(); + } + }, + bindReadPresenceEvents() { + if (this.readPresenceEventsBound || typeof window === 'undefined' || typeof document === 'undefined') { + return; + } + this.readPresenceEventsBound = true; + const syncWhenReadable = () => { + if (canMarkReadNow()) { + this.syncActiveMessages({ allowReadReceipt: true }); + } + }; + window.addEventListener('focus', syncWhenReadable); + document.addEventListener('visibilitychange', syncWhenReadable); + }, + async refreshRemoteConversations() { + this.bindImEvents(); + this.usingDemoData = false; + if (this.conversationSyncInFlight) { + return; + } + this.conversationSyncInFlight = true; + try { + const remoteConversations = await imClient.fetchRemoteConversations(50); + this.applyRemoteConversations(remoteConversations); + } catch (error) { + imClient.refreshConversationList(); + } finally { + this.conversationSyncInFlight = false; + } + }, + setActionNotice(message) { + this.actionNotice = message; + this.actionError = ''; + }, + setActionError(message) { + this.actionError = message; + this.actionNotice = ''; + }, + focusMessage(message) { + const key = messageStableKey(message); + if (!key) { + return; + } + this.attentionMessageId = key; + window.clearTimeout(this.attentionMessageTimer); + this.attentionMessageTimer = window.setTimeout(() => { + if (this.attentionMessageId === key) { + this.attentionMessageId = ''; + } + }, 2800); + }, + async handleMessageAction({ action, message }) { + if (!message) { + return; + } + if (action === 'copy') { + await this.copyMessages([message]); + } else if (action === 'edit') { + this.startEditMessage(message); + } else if (action === 'delete') { + this.deleteMessages([message]); + } else if (action === 'forward') { + this.startForward([message]); + } else if (action === 'favorite') { + this.toggleFavorite(message); + } else if (action === 'detail') { + await this.openMessageDetail(message); + } else if (action === 'quote') { + this.quoteMessage(message); + } else if (action === 'select') { + this.enterSelectionMode(message); + } else if (action === 'recall') { + await this.recallMessage(message); + } + }, + async openMessageDetail(message) { + const conversation = this.activeConversation; + if (!conversation || !message) { + return; + } + const key = messageStableKey(message); + this.messageDetailOpen = true; + this.messageDetailLoading = true; + this.messageDetailError = ''; + this.messageDetail = { + messageUid: key, + conversationType: conversation.type || conversation.raw?.type || 'single', + sentAt: Number(message.displayTimestamp || message.timestamp || 0), + editedAt: Number(message.editedAt || 0), + recalled: isRecalledMessage(message), + localOnly: true, + sender: { + uid: message.from || '', + name: message.senderName || message.from || '' + } + }; + try { + if (!/^\d+$/.test(key)) { + this.messageDetailError = '本地临时消息暂无送达/已读详情'; + return; + } + const detail = await imClient.getMessageDetail(conversation, message); + this.messageDetail = detail || this.messageDetail; + } catch (error) { + this.messageDetailError = error.message || '消息详情加载失败'; + } finally { + this.messageDetailLoading = false; + } + }, + closeMessageDetail() { + this.messageDetailOpen = false; + this.messageDetailLoading = false; + this.messageDetailError = ''; + this.messageDetail = null; + }, + async copyMessages(messages) { + const text = messages.map(message => message.text || '').join('\n'); + if (!text.trim()) { + return; + } + try { + await writeClipboard(text); + this.setActionNotice(messages.length > 1 ? '已复制所选消息' : '已复制'); + } catch (error) { + this.setActionError('复制失败'); + } + }, + deleteMessages(messages) { + const conversation = this.activeConversation; + if (!conversation || !messages.length) { + return; + } + imClient.hideMessages(conversation, messages); + this.removeMessages(conversation.id, messages); + if (!this.selectedMessageIds.length) { + this.selectionMode = false; + } + this.setActionNotice(messages.length > 1 ? '已删除所选消息' : '已删除'); + }, + toggleFavorite(message) { + const key = messageStableKey(message); + if (!key) { + return; + } + const index = this.favoriteMessages.findIndex(item => item.key === key); + if (index >= 0) { + this.favoriteMessages.splice(index, 1); + if (this.activeFavoriteKey === key) { + this.activeFavoriteKey = this.favoriteMessages[0]?.key || ''; + } + this.setActionNotice('已取消收藏'); + } else { + const conversation = this.conversations.find(item => item.id === message.conversationId) || {}; + this.favoriteMessages.unshift({ + key, + id: message.id, + messageUid: message.messageUid, + conversationId: message.conversationId, + conversationTitle: conversation.title || message.conversationTitle || '', + type: message.type || 'text', + text: message.text || '', + preview: messagePreviewText(message), + senderName: message.senderName || message.from || '', + timestamp: Number(message.displayTimestamp || message.timestamp || Date.now()), + editedAt: Number(message.editedAt || 0), + recalled: isRecalledMessage(message), + recallStatus: message.recallStatus || (isRecalledMessage(message) ? '已撤回' : ''), + attachment: message.attachment ? { ...message.attachment } : null + }); + this.activeFavoriteKey = key; + this.setActionNotice('已收藏'); + } + this.favoriteMessages = this.favoriteMessages.slice(0, 200); + writeFavoriteMessages(this.favoriteMessages); + }, + setActiveFavorite(key) { + this.activeFavoriteKey = String(key || ''); + }, + removeFavorite(key) { + const value = String(key || ''); + if (!value) { + return; + } + this.favoriteMessages = this.favoriteMessages.filter(message => message.key !== value); + if (this.activeFavoriteKey === value) { + this.activeFavoriteKey = this.favoriteMessages[0]?.key || ''; + } + writeFavoriteMessages(this.favoriteMessages); + }, + quoteMessage(message) { + this.quotedMessage = { + ...message, + quoteText: quotePrefix(message) + }; + this.setActionNotice('已引用,输入后发送'); + }, + cancelQuote() { + this.quotedMessage = null; + }, + enterSelectionMode(message = null) { + this.selectionMode = true; + if (message) { + const key = messageStableKey(message); + this.selectedMessageIds = key ? [key] : []; + } + }, + toggleSelectedMessage(message) { + const key = messageStableKey(message); + if (!key) { + return; + } + const selected = new Set(this.selectedMessageIds); + if (selected.has(key)) { + selected.delete(key); + } else { + selected.add(key); + } + this.selectedMessageIds = Array.from(selected); + this.selectionMode = this.selectedMessageIds.length > 0; + }, + clearSelection() { + this.selectionMode = false; + this.selectedMessageIds = []; + }, + copySelectedMessages() { + return this.copyMessages(this.selectedMessages); + }, + deleteSelectedMessages() { + this.deleteMessages(this.selectedMessages); + }, + startForward(messages) { + this.forwardingMessages = messages.filter(message => message?.text); + if (!this.forwardingMessages.length) { + this.setActionError('没有可转发的消息'); + } + }, + startForwardSelectedMessages() { + this.startForward(this.selectedMessages); + }, + cancelForward() { + this.forwardingMessages = []; + }, + startEditMessage(message) { + const isResend = isRecalledMessage(message); + if (!isResend && !message?.text) { + this.setActionError('没有可编辑的文字内容'); + return; + } + this.editingMessage = message; + this.editingText = message.text || ''; + this.editingMode = isResend ? 'resend' : 'edit'; + }, + cancelEditMessage() { + this.editingMessage = null; + this.editingText = ''; + this.editingMode = ''; + }, + async completeEditMessage() { + const conversation = this.activeConversation; + const message = this.editingMessage; + const text = this.editingText.trim(); + const isResend = this.editingMode === 'resend' || isRecalledMessage(message); + if (!conversation || !message) { + this.cancelEditMessage(); + return; + } + if (!text) { + this.setActionError('编辑内容不能为空'); + return; + } + if (!isResend && text === String(message.text || '').trim()) { + this.cancelEditMessage(); + return; + } + try { + if (isResend) { + await this.sendTextToConversation(conversation, text); + this.cancelEditMessage(); + this.setActionNotice('已重新发送'); + return; + } + const editedMessage = await imClient.editMessage(conversation, message, text); + this.replaceLocalMessage(conversation.id, message, editedMessage); + if (messageActivityTimestamp(editedMessage) >= Number(conversation.updatedAt || 0)) { + conversation.subtitle = editedMessage.text; + conversation.updatedAt = messageActivityTimestamp(editedMessage); + writeLocalConversations(this.conversations); + } + this.cancelEditMessage(); + this.setActionNotice('已编辑'); + } catch (error) { + this.setActionError(error.message || '编辑失败'); + } + }, + async completeForward(targetConversationId) { + const target = this.conversations.find(conversation => conversation.id === targetConversationId); + if (!target || !this.forwardingMessages.length) { + return; + } + const text = this.forwardingMessages + .map(message => `转发:${message.text || ''}`) + .join('\n\n'); + try { + await this.sendTextToConversation(target, text); + this.forwardingMessages = []; + this.clearSelection(); + this.setActionNotice('已转发'); + } catch (error) { + this.setActionError(error.message || '转发失败'); + } + }, + async recallMessage(message) { + const conversation = this.activeConversation; + if (!conversation) { + return; + } + try { + await imClient.recallMessage(conversation, message); + if (message.direction !== 'out') { + this.removeMessages(conversation.id, [message]); + this.setActionNotice('已撤回'); + this.syncActiveMessages(); + return; + } + this.replaceLocalMessage(conversation.id, message, { + status: 'recalled', + deliveryStatus: '', + recalled: true, + recallStatus: '已撤回' + }); + this.setActionNotice('已撤回'); + } catch (error) { + this.setActionError(error.message || '撤回失败'); + } + }, + async createGroup(options = {}) { + const session = useSessionStore(); + if (!session.userId) { + this.setActionError('请先登录'); + return false; + } + try { + const conversation = await imClient.createGroup(session.userId, options); + this.upsertConversation(conversation); + this.setActiveConversation(conversation.id); + this.setActionNotice(options.channel ? '公告群已创建' : '群组已创建'); + this.refreshRemoteConversations(); + return true; + } catch (error) { + this.setActionError(error.message || (options.channel ? '创建公告群失败' : '创建群组失败')); + return false; + } + }, + async joinGroup(groupKey) { + const session = useSessionStore(); + if (!session.userId) { + this.setActionError('请先登录'); + return false; + } + try { + const conversation = await imClient.joinGroup(session.userId, groupKey); + this.upsertConversation(conversation); + this.setActiveConversation(conversation.id); + this.setActionNotice('已加入群组'); + this.refreshRemoteConversations(); + return true; + } catch (error) { + this.setActionError(error.message || '加入群组失败'); + return false; + } + }, + async broadcastMessage(targetIds, text) { + const session = useSessionStore(); + if (!session.userId) { + this.setActionError('请先登录'); + return false; + } + try { + const result = await imClient.broadcastMessage(session.userId, targetIds, text); + const successCount = Number(result.successCount || 0); + const failedCount = Number(result.failedCount || 0); + this.setActionNotice(failedCount > 0 ? `已群发 ${successCount} 条,失败 ${failedCount} 条` : `已群发 ${successCount} 条`); + this.refreshRemoteConversations(); + return true; + } catch (error) { + this.setActionError(error.message || '群发失败'); + return false; + } + }, + async leaveGroup(conversation) { + const session = useSessionStore(); + if (!session.userId || !conversation?.target) { + this.setActionError('缺少群组信息'); + return false; + } + try { + await imClient.leaveGroup(session.userId, conversation.target); + this.removeConversation(conversation.id); + this.setActionNotice('已退出群组'); + this.refreshRemoteConversations(); + return true; + } catch (error) { + this.setActionError(error.message || '退出群组失败'); + return false; + } + }, + async dismissGroup(conversation) { + const session = useSessionStore(); + if (!session.userId || !conversation?.target) { + this.setActionError('缺少群组信息'); + return false; + } + try { + await imClient.dismissGroup(session.userId, conversation.target); + this.removeConversation(conversation.id); + this.setActionNotice('群组已解散'); + this.refreshRemoteConversations(); + return true; + } catch (error) { + this.setActionError(error.message || '解散群组失败'); + return false; + } + } + } +}); diff --git a/src/stores/navigation.js b/src/stores/navigation.js new file mode 100644 index 0000000..fa0daa0 --- /dev/null +++ b/src/stores/navigation.js @@ -0,0 +1,18 @@ +import { defineStore } from 'pinia'; + +export const useNavigationStore = defineStore('navigation', { + state: () => ({ + section: 'messages' + }), + actions: { + showMessages() { + this.section = 'messages'; + }, + showContacts() { + this.section = 'contacts'; + }, + showFavorites() { + this.section = 'favorites'; + } + } +}); diff --git a/src/stores/session.js b/src/stores/session.js new file mode 100644 index 0000000..3bc2a92 --- /dev/null +++ b/src/stores/session.js @@ -0,0 +1,225 @@ +import { defineStore } from 'pinia'; +import { imClient } from '@/services/imClient'; +import { appConfig } from '@/config'; + +const STORAGE_KEY = 'qmt-web-im-session'; + +function normalizeStoredPortrait(portrait) { + return portrait || ''; +} + +function nextProfilePortrait(profilePortrait, currentPortrait) { + return normalizeStoredPortrait(profilePortrait) || normalizeStoredPortrait(currentPortrait); +} + +function inferAccountType(account) { + return String(account || '').includes('@') ? 2 : 1; +} + +export const useSessionStore = defineStore('session', { + state: () => ({ + isAuthenticated: false, + connecting: false, + connected: false, + connectionStatusText: '未连接', + userId: '', + token: '', + displayName: '', + portrait: '', + error: '', + sdkVersion: '', + eventsBound: false + }), + actions: { + restore() { + const raw = localStorage.getItem(STORAGE_KEY); + if (!raw) { + return; + } + + try { + const session = JSON.parse(raw); + if (session.appServer !== appConfig.appServer) { + localStorage.removeItem(STORAGE_KEY); + return; + } + + if (session.userId && session.token) { + if (!session.clientId) { + localStorage.removeItem(STORAGE_KEY); + return; + } + this.userId = session.userId; + this.token = session.token; + this.displayName = session.displayName || session.userId; + this.portrait = normalizeStoredPortrait(session.portrait); + this.isAuthenticated = true; + imClient.restoreClientId(session.clientId); + this.connect(); + this.refreshProfile(); + } + } catch (error) { + localStorage.removeItem(STORAGE_KEY); + } + }, + + async loginWithPassword(account, password, type = inferAccountType(account)) { + this.error = ''; + this.connecting = true; + try { + const result = await imClient.loginWithPassword(account, password, type); + this.applyLoginResult(result); + await this.connect(); + } catch (error) { + this.error = error.message || '登录失败'; + } finally { + this.connecting = false; + } + }, + + async loginWithCode(account, code) { + this.error = ''; + this.connecting = true; + try { + const result = await imClient.loginWithCode(account, code); + this.applyLoginResult(result); + await this.connect(); + } catch (error) { + this.error = error.message || '登录失败'; + } finally { + this.connecting = false; + } + }, + + async completePcSessionLogin(result) { + this.error = ''; + this.connecting = true; + try { + this.applyLoginResult(result); + await this.connect(); + } catch (error) { + this.error = error.message || '扫码登录失败'; + } finally { + this.connecting = false; + } + }, + + async register(payload) { + this.error = ''; + this.connecting = true; + try { + const result = await imClient.register(payload); + this.applyLoginResult(result); + await this.connect(); + return result; + } catch (error) { + this.error = error.message || '注册失败'; + return null; + } finally { + this.connecting = false; + } + }, + + applyLoginResult(result) { + this.userId = result.userId || result.uid || result.id || ''; + this.token = result.token || result.imToken || ''; + this.displayName = result.displayName || result.userName || result.name || this.userId; + this.portrait = normalizeStoredPortrait(result.portrait); + this.isAuthenticated = Boolean(this.userId && this.token); + this.persistSession(); + }, + + persistSession() { + localStorage.setItem(STORAGE_KEY, JSON.stringify({ + userId: this.userId, + token: this.token, + displayName: this.displayName, + portrait: this.portrait, + clientId: imClient.getClientId(), + appServer: appConfig.appServer + })); + }, + + applyProfile(profile) { + if (!profile) { + return; + } + this.userId = profile.userId || profile.uid || this.userId; + this.displayName = profile.displayName || profile.name || this.displayName || this.userId; + this.portrait = nextProfilePortrait(profile.portrait, this.portrait); + this.persistSession(); + }, + + async refreshProfile() { + if (!this.userId) { + return null; + } + try { + const profile = await imClient.getProfile(this.userId); + this.applyProfile(profile); + return profile; + } catch (error) { + return null; + } + }, + + async saveProfile({ displayName, portraitFile }) { + if (!this.userId) { + throw new Error('请先登录'); + } + const profile = await imClient.updateProfile(this.userId, { + displayName, + portraitFile + }); + this.applyProfile(profile); + return profile; + }, + + async changePassword({ oldPassword, newPassword }) { + if (!this.userId) { + throw new Error('请先登录'); + } + await imClient.changePassword({ oldPassword, newPassword }); + await this.logout(); + return true; + }, + + async resetPasswordWithCode({ mobile, resetCode, newPassword }) { + await imClient.resetPasswordWithCode({ mobile, resetCode, newPassword }); + return true; + }, + + async connect() { + if (!this.userId || !this.token) { + return; + } + await imClient.ensureSdk(); + this.sdkVersion = imClient.getSdkVersion(); + this.bindImEvents(); + await imClient.connect(this.userId, this.token); + }, + + bindImEvents() { + if (this.eventsBound) { + return; + } + this.eventsBound = true; + imClient.on('connectionStatusChanged', status => { + this.connected = status.connected; + this.connectionStatusText = status.statusText || (status.connected ? '已连接' : '未连接'); + }); + }, + + async logout() { + try { + await imClient.logout?.(); + } catch (error) { + this.error = error.message || '退出登录失败'; + } finally { + imClient.disconnect(); + localStorage.removeItem(STORAGE_KEY); + this.$reset(); + } + } + } +}); diff --git a/src/styles/base.css b/src/styles/base.css new file mode 100644 index 0000000..dc70141 --- /dev/null +++ b/src/styles/base.css @@ -0,0 +1,3022 @@ +:root { + color-scheme: light; + --bg: #f4f6f8; + --panel: #ffffff; + --panel-soft: #f8fafc; + --line: #d9e0e7; + --text: #18212f; + --muted: #6a7584; + --primary: #177d72; + --primary-strong: #0f625a; + --accent: #d84e3f; + --bubble-in: #ffffff; + --bubble-out: #dff4ef; + --rail: #202a33; +} + +* { + box-sizing: border-box; +} + +html, +body, +#app { + width: 100%; + height: 100%; + overflow: hidden; +} + +body { + margin: 0; + min-width: 320px; + background: var(--bg); + color: var(--text); + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; +} + +button, +input, +textarea { + font: inherit; +} + +button { + cursor: pointer; +} + +.login-page { + min-height: 100vh; + display: grid; + place-items: center; + padding: 24px; + background: linear-gradient(135deg, #eef6f3 0%, #f7f3ee 55%, #eef2f7 100%); +} + +.login-panel { + width: min(420px, 100%); + padding: 32px; + background: var(--panel); + border: 1px solid var(--line); + border-radius: 8px; + box-shadow: 0 18px 48px rgba(30, 40, 50, 0.12); +} + +.brand-lockup { + display: flex; + align-items: center; + gap: 14px; + margin-bottom: 28px; +} + +.brand-mark, +.rail-avatar, +.conversation-avatar { + display: grid; + place-items: center; + flex: 0 0 auto; +} + +.image-avatar { + object-fit: cover; +} + +.brand-mark { + width: 48px; + height: 48px; + border-radius: 8px; + object-fit: contain; + background: #fff; +} + +.brand-lockup h1, +.list-header h2, +.chat-header h2 { + margin: 0; + font-size: 20px; +} + +.brand-lockup p, +.list-header p, +.chat-header p { + margin: 4px 0 0; + color: var(--muted); + font-size: 13px; +} + +.login-form { + display: grid; + gap: 16px; +} + +.login-tabs, +.code-row, +.phone-row, +.captcha-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; +} + +.login-tabs button, +.code-action { + height: 38px; + border: 1px solid var(--line); + border-radius: 6px; + background: #fff; + color: var(--text); +} + +.login-tabs button.active { + border-color: var(--primary); + background: #e8f4f2; + color: var(--primary-strong); + font-weight: 700; +} + +.login-tabs { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.login-tabs button { + min-width: 0; + padding: 0 6px; + overflow-wrap: anywhere; +} + +.code-row { + grid-template-columns: minmax(0, 1fr) 112px; +} + +.captcha-row { + grid-template-columns: minmax(0, 1fr) 112px; +} + +.phone-row { + grid-template-columns: 86px minmax(0, 1fr); +} + +.country-code-field { + height: 42px; + display: grid; + grid-template-columns: 24px minmax(0, 1fr); + align-items: center; + border: 1px solid var(--line); + border-radius: 6px; + background: #fff; + color: var(--text); + overflow: hidden; +} + +.country-code-field:focus-within { + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(23, 125, 114, 0.12); +} + +.country-code-field span { + display: grid; + place-items: center; + color: var(--muted); + font-weight: 700; +} + +.code-action:disabled { + cursor: not-allowed; + opacity: 0.55; +} + +.login-form label { + display: grid; + gap: 8px; + color: var(--muted); + font-size: 13px; +} + +.label-row { + min-height: 18px; + display: grid; + grid-template-columns: 86px minmax(0, 1fr); + align-items: center; + gap: 8px; +} + +.field-error-badge { + min-height: 28px; + display: grid; + place-items: center; + padding: 4px 10px; + border-radius: 2px; + background: var(--accent); + color: #fff; + font-weight: 700; + line-height: 1.2; + text-align: center; +} + +.login-form input, +.login-form select, +.modal-panel input, +.modal-panel textarea, +.search-box input, +.message-composer textarea { + width: 100%; + border: 1px solid var(--line); + border-radius: 6px; + outline: none; + background: #fff; + color: var(--text); +} + +.login-form input, +.login-form select, +.modal-panel input, +.search-box input { + height: 42px; + padding: 0 12px; +} + +.modal-panel textarea { + min-height: 92px; + resize: vertical; + padding: 10px 12px; + line-height: 1.5; +} + +.login-form .country-code-field input { + height: 40px; + padding: 0 10px 0 0; + border: 0; + border-radius: 0; +} + +.login-form .country-code-field input:focus { + border-color: transparent; + box-shadow: none; +} + +.login-form input:focus, +.login-form select:focus, +.modal-panel input:focus, +.modal-panel textarea:focus, +.search-box input:focus, +.message-composer textarea:focus { + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(23, 125, 114, 0.12); +} + +.login-form input.captcha-input-valid { + border-color: var(--primary); + background: var(--primary); + color: #fff; + font-weight: 700; +} + +.login-form input.captcha-input-valid::placeholder { + color: rgba(255, 255, 255, 0.75); +} + +.login-form input.captcha-input-invalid { + border-color: var(--accent); + color: var(--accent); + box-shadow: 0 0 0 3px rgba(216, 78, 63, 0.12); +} + +.login-form input.field-input-error { + border-color: var(--accent); + color: var(--accent); + box-shadow: 0 0 0 3px rgba(216, 78, 63, 0.12); +} + +.login-form input.field-input-error::placeholder { + color: rgba(216, 78, 63, 0.78); +} + +.captcha-button { + width: 112px; + height: 42px; + padding: 0; + border: 1px solid var(--line); + border-radius: 6px; + background: #f7fbfa; + overflow: hidden; +} + +.captcha-button img { + display: block; + width: 100%; + height: 100%; + object-fit: contain; +} + +.qr-login-card { + position: relative; + width: min(248px, 100%); + aspect-ratio: 1; + display: grid; + place-items: center; + justify-self: center; + border: 1px solid var(--line); + border-radius: 8px; + background: #fff; + overflow: hidden; +} + +.qr-code-button { + width: 100%; + height: 100%; + display: grid; + place-items: center; + padding: 18px; + border: 0; + background: #fff; + color: var(--muted); + font-weight: 700; +} + +.qr-code-button:disabled { + cursor: default; +} + +.qr-code-button img { + width: 100%; + height: 100%; + object-fit: contain; + image-rendering: pixelated; +} + +.qr-login-card.scanned .qr-code-button, +.qr-login-card.expired .qr-code-button, +.qr-login-card.error .qr-code-button { + opacity: 0.22; +} + +.qr-scan-user { + position: absolute; + inset: 0; + display: grid; + place-items: center; + background: rgba(255, 255, 255, 0.76); +} + +.qr-scan-user img, +.qr-scan-user span { + width: 70px; + height: 70px; + display: grid; + place-items: center; + border-radius: 8px; + background: var(--primary); + color: #fff; + font-size: 28px; + font-weight: 800; + object-fit: cover; +} + +.qr-login-status { + min-height: 48px; + display: grid; + gap: 4px; + text-align: center; +} + +.qr-login-status strong { + color: var(--text); + font-size: 15px; +} + +.qr-login-status span { + color: var(--muted); + font-size: 13px; +} + +.primary-action, +.primary-inline-action, +.send-button { + border: 0; + border-radius: 6px; + background: var(--primary); + color: #fff; + font-weight: 700; +} + +.primary-action { + height: 44px; +} + +.primary-action:hover, +.primary-inline-action:hover, +.send-button:hover { + background: var(--primary-strong); +} + +.primary-action:disabled, +.primary-inline-action:disabled, +.send-button:disabled { + cursor: not-allowed; + opacity: 0.55; +} + +.ghost-action { + width: 100%; + height: 40px; + margin-top: 12px; + border: 1px solid var(--line); + border-radius: 6px; + background: #fff; + color: var(--text); +} + +.ghost-inline-action, +.primary-inline-action, +.danger-inline-action { + height: 38px; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0 16px; + border-radius: 6px; + font-weight: 700; + line-height: 1; +} + +.ghost-inline-action { + border: 1px solid var(--line); + background: #fff; + color: var(--text); +} + +.ghost-inline-action:hover { + background: #edf2f5; +} + +.danger-inline-action { + border: 1px solid #ef4444; + background: #fff; + color: #b91c1c; +} + +.danger-inline-action:hover { + background: #fee2e2; +} + +.form-error { + margin: 0; + color: var(--accent); + font-size: 13px; +} + +.form-info { + margin: 0; + color: var(--muted); + font-size: 13px; +} + +.app-shell { + height: 100dvh; + min-height: 0; + display: grid; + grid-template-columns: 64px minmax(260px, 340px) minmax(0, 1fr); + overflow: hidden; + background: var(--panel); +} + +.documented-shell { + min-height: 100dvh; + display: grid; + grid-template-columns: 260px minmax(0, 1fr); + background: + radial-gradient(circle at top left, rgba(23, 125, 114, 0.08), transparent 28%), + linear-gradient(180deg, #f4f8f7 0%, #eef3f7 100%); +} + +.documented-rail { + min-height: 0; + display: grid; + align-content: start; + gap: 14px; + padding: 24px 20px; + border-right: 1px solid var(--line); + background: rgba(255, 255, 255, 0.82); + backdrop-filter: blur(12px); +} + +.documented-avatar { + width: 64px; + height: 64px; + display: grid; + place-items: center; + overflow: hidden; + border-radius: 16px; + background: #dff4ef; + color: var(--primary-strong); + font-size: 28px; + font-weight: 800; +} + +.documented-avatar img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.documented-rail-copy { + display: grid; + gap: 4px; +} + +.documented-rail-copy strong { + font-size: 16px; +} + +.documented-rail-copy small { + color: var(--muted); + overflow-wrap: anywhere; +} + +.documented-rail-button { + min-height: 40px; + padding: 0 14px; + border: 0; + border-radius: 10px; + background: var(--primary); + color: #fff; + font-weight: 700; + text-align: left; +} + +.documented-rail-button.ghost { + border: 1px solid var(--line); + background: #fff; + color: var(--text); +} + +.documented-main { + min-width: 0; + min-height: 0; + overflow: auto; + padding: 28px; + display: grid; + align-content: start; + gap: 18px; +} + +.documented-header { + display: flex; + align-items: end; + justify-content: space-between; + gap: 18px; +} + +.documented-header h1 { + margin: 0; + font-size: 28px; +} + +.documented-header p { + margin: 6px 0 0; + color: var(--muted); +} + +.documented-status { + min-width: 180px; + display: grid; + gap: 4px; + padding: 14px 16px; + border: 1px solid var(--line); + border-radius: 14px; + background: rgba(255, 255, 255, 0.88); +} + +.documented-status span { + color: var(--muted); + font-size: 12px; +} + +.documented-status strong { + font-size: 15px; +} + +.documented-card { + display: grid; + gap: 12px; + padding: 20px; + border: 1px solid var(--line); + border-radius: 16px; + background: rgba(255, 255, 255, 0.88); + box-shadow: 0 16px 36px rgba(24, 33, 47, 0.06); +} + +.documented-card h2 { + margin: 0; + font-size: 18px; +} + +.documented-text { + margin: 0; + color: var(--muted); + line-height: 1.6; +} + +.documented-list { + margin: 0; + padding-left: 18px; + display: grid; + gap: 8px; + line-height: 1.55; +} + +.documented-list.muted { + color: var(--muted); +} + +.documented-meta { + margin: 0; + display: grid; + gap: 10px; +} + +.documented-meta div { + display: grid; + grid-template-columns: 110px minmax(0, 1fr); + gap: 12px; + padding-top: 10px; + border-top: 1px solid var(--line); +} + +.documented-meta dt { + color: var(--muted); +} + +.documented-meta dd { + margin: 0; + overflow-wrap: anywhere; +} + +.documented-password-panel { + width: min(100%, 360px); +} + +.navigation-rail { + min-height: 0; + display: flex; + flex-direction: column; + align-items: center; + gap: 14px; + padding: 16px 10px; + background: var(--rail); +} + +.rail-avatar { + width: 40px; + height: 40px; + border-radius: 8px; + background: #e9f7f4; + color: var(--primary-strong); + font-weight: 800; + overflow: hidden; +} + +.rail-avatar-button { + position: relative; + border: 0; + padding: 0; + cursor: pointer; +} + +.rail-avatar-button::after { + content: "+"; + position: absolute; + right: -2px; + bottom: -2px; + width: 15px; + height: 15px; + display: grid; + place-items: center; + border: 1px solid var(--rail); + border-radius: 50%; + background: var(--primary); + color: #fff; + font-size: 11px; + font-weight: 800; + line-height: 1; +} + +.rail-avatar-button:hover, +.rail-avatar-button:focus-visible { + box-shadow: 0 0 0 3px rgba(47, 196, 178, 0.28); +} + +.rail-avatar img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.rail-button, +.icon-button { + display: grid; + place-items: center; + border: 0; + border-radius: 6px; + background: transparent; +} + +.rail-button { + width: 40px; + height: 40px; + color: #dbe5ea; + font-size: 18px; +} + +.rail-button svg { + width: 22px; + height: 22px; + fill: none; + stroke: currentColor; + stroke-width: 1.8; + stroke-linecap: round; + stroke-linejoin: round; +} + +.rail-button.active, +.rail-button:hover { + background: rgba(255, 255, 255, 0.12); +} + +.rail-logout-button:hover { + background: rgba(239, 68, 68, 0.2); + color: #fecaca; +} + +.rail-spacer { + flex: 1; +} + +.conversation-list { + position: relative; + min-height: 0; + display: grid; + grid-template-rows: 72px auto minmax(0, 1fr); + border-right: 1px solid var(--line); + background: var(--panel-soft); + overflow: hidden; +} + +.modal-backdrop { + position: absolute; + inset: 0; + z-index: 5; + display: grid; + place-items: start center; + padding: 88px 16px 16px; + background: rgba(24, 33, 47, 0.28); +} + +.modal-panel { + width: min(100%, 308px); + display: grid; + gap: 14px; + padding: 16px; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--panel); + box-shadow: 0 18px 40px rgba(24, 33, 47, 0.18); +} + +.modal-panel header, +.modal-actions { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; +} + +.modal-panel header strong { + font-size: 16px; +} + +.modal-panel label { + display: grid; + gap: 8px; + color: var(--muted); + font-size: 13px; +} + +.settings-backdrop { + z-index: 24; + place-items: center; + padding: 24px 16px; +} + +.settings-panel { + width: min(620px, calc(100vw - 32px)); + max-height: calc(100dvh - 48px); + display: grid; + gap: 14px; + overflow: auto; + padding: 16px; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--panel); + box-shadow: 0 18px 40px rgba(24, 33, 47, 0.18); +} + +.settings-panel header, +.settings-password-form footer { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.settings-panel header strong { + font-size: 16px; +} + +.settings-layout { + display: grid; + grid-template-columns: 170px minmax(0, 1fr); + gap: 14px; +} + +.settings-menu { + display: grid; + align-content: start; + gap: 8px; +} + +.settings-menu button { + min-height: 46px; + display: grid; + gap: 2px; + justify-items: start; + padding: 8px 10px; + border: 1px solid var(--line); + border-radius: 6px; + background: #fff; + color: var(--text); + text-align: left; +} + +.settings-menu button.active, +.settings-menu button:hover { + border-color: var(--primary); + background: #e8f4f2; + color: var(--primary-strong); +} + +.settings-menu button span { + font-weight: 700; +} + +.settings-menu button small { + color: var(--muted); + font-size: 11px; +} + +.settings-password-form, +.settings-placeholder { + min-height: 250px; + display: grid; + align-content: start; + gap: 12px; +} + +.settings-password-form h3, +.settings-placeholder h3 { + margin: 0; + font-size: 16px; +} + +.settings-password-form label { + display: grid; + gap: 8px; + color: var(--muted); + font-size: 13px; +} + +.settings-password-form input { + width: 100%; + height: 42px; + padding: 0 12px; + border: 1px solid var(--line); + border-radius: 6px; + outline: none; + background: #fff; + color: var(--text); +} + +.settings-password-form input:focus { + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(23, 125, 114, 0.12); +} + +.settings-password-form footer { + margin-top: 4px; + justify-content: flex-end; + flex-wrap: wrap; +} + +.settings-password-form footer .inline-save-status { + margin-right: auto; +} + +.settings-placeholder { + place-items: center; + border: 1px dashed var(--line); + border-radius: 8px; + color: var(--muted); + text-align: center; +} + +.settings-placeholder p { + margin: 0; +} + +.profile-edit-panel { + width: min(100%, 360px); +} + +.profile-avatar-field { + display: grid; + justify-items: center; + gap: 10px; +} + +.profile-avatar-field input[type="file"] { + position: absolute; + inline-size: 1px; + block-size: 1px; + opacity: 0; + pointer-events: none; +} + +.profile-avatar-preview { + width: 88px; + height: 88px; + display: grid; + place-items: center; + overflow: hidden; + padding: 0; + border: 0; + border-radius: 8px; + background: #dce8f2; + color: #24435c; + cursor: default; + font-size: 30px; + font-weight: 800; +} + +.profile-avatar-preview.clickable { + cursor: zoom-in; +} + +.profile-avatar-preview.clickable:hover { + box-shadow: 0 0 0 3px rgba(23, 125, 114, 0.16); +} + +.profile-avatar-preview img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.profile-avatar-change-button { + min-width: 96px; +} + +.avatar-preview-backdrop { + z-index: 32; + place-items: center; + padding: 24px 16px; +} + +.avatar-preview-dialog { + width: min(520px, calc(100vw - 32px)); + max-height: calc(100dvh - 48px); + display: grid; + gap: 12px; + padding: 14px; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--panel); + box-shadow: 0 18px 40px rgba(24, 33, 47, 0.18); +} + +.avatar-preview-dialog header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.avatar-preview-actions { + display: flex; + align-items: center; + gap: 6px; +} + +.avatar-preview-actions button { + min-width: 34px; + height: 34px; + display: inline-flex; + align-items: center; + justify-content: center; + border: 1px solid var(--line); + border-radius: 6px; + background: #fff; + color: var(--text); + font-weight: 700; +} + +.avatar-preview-actions .icon-button { + border: 0; + color: var(--muted); +} + +.avatar-preview-stage { + height: min(430px, calc(100dvh - 160px)); + min-height: 260px; + display: grid; + place-items: center; + overflow: hidden; + border-radius: 8px; + background: #101820; + cursor: zoom-in; + touch-action: none; +} + +.avatar-preview-stage.dragging { + cursor: grabbing; +} + +.avatar-preview-stage img { + max-width: min(100%, 430px); + max-height: 100%; + border-radius: 8px; + object-fit: contain; + transform-origin: center center; + transition: transform 0.08s ease-out; + user-select: none; + pointer-events: none; +} + +.avatar-preview-stage.dragging img { + transition: none; +} + +.avatar-cropper-backdrop { + z-index: 30; + place-items: center; + padding: 24px 16px; +} + +.avatar-cropper-panel { + width: min(720px, calc(100vw - 32px)); + max-height: calc(100dvh - 48px); + overflow: auto; +} + +.avatar-cropper-header-actions { + display: flex; + align-items: center; + gap: 8px; +} + +.avatar-cropper-layout { + display: grid; + grid-template-columns: minmax(0, 1fr) 190px; + gap: 14px; +} + +.avatar-cropper-stage { + position: relative; + height: 360px; + min-height: 360px; + overflow: hidden; + border: 1px dashed #b8ccd7; + border-radius: 8px; + background: #0f1b24; + touch-action: none; + cursor: grab; +} + +.avatar-cropper-stage.dragging { + cursor: grabbing; +} + +.avatar-cropper-stage canvas { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + display: block; +} + +.avatar-cropper-box { + position: absolute; + left: 50%; + top: 50%; + width: 74%; + aspect-ratio: 1; + max-width: calc(100% - 44px); + max-height: calc(100% - 44px); + transform: translate(-50%, -50%); + border: 2px dashed var(--primary); + border-radius: 8px; + box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.72); + pointer-events: none; +} + +.avatar-cropper-mask { + position: absolute; + inset: 0; + pointer-events: none; +} + +.avatar-cropper-placeholder { + position: absolute; + inset: 0; + display: grid; + place-items: center; + color: #d9e8ee; + font-size: 13px; + pointer-events: none; +} + +.avatar-cropper-controls { + min-width: 0; + display: grid; + align-content: start; + gap: 12px; +} + +.avatar-cropper-tools { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 8px; +} + +.avatar-cropper-tools button { + min-height: 34px; + padding: 0 10px; + border: 1px solid var(--line); + border-radius: 6px; + background: #fff; + color: var(--text); + font-weight: 700; +} + +.avatar-cropper-tools button:hover { + border-color: var(--primary); + color: var(--primary-strong); +} + +.avatar-cropper-preview { + width: 108px; + height: 108px; + justify-self: center; + overflow: hidden; + border: 1px solid var(--line); + border-radius: 8px; + background: #eef4f7; +} + +.avatar-cropper-preview canvas { + width: 100%; + height: 100%; + display: block; +} + +.avatar-cropper-controls footer { + display: flex; + align-items: center; + justify-content: flex-end; + flex-wrap: wrap; + gap: 8px; +} + +.avatar-cropper-controls footer .inline-save-status { + flex: 1 1 100%; +} + +.modal-actions { + justify-content: flex-end; +} + +.list-header, +.chat-header { + display: flex; + align-items: center; + justify-content: space-between; + min-height: 72px; + padding: 16px 18px; + border-bottom: 1px solid var(--line); +} + +.icon-button { + width: 34px; + height: 34px; + color: var(--muted); + font-size: 20px; +} + +.icon-button:hover { + background: #edf2f5; + color: var(--text); +} + +.search-box { + padding: 12px 14px; +} + +.sidebar-scroll { + min-height: 0; + overflow: auto; + overscroll-behavior: contain; +} + +.conversation-item { + width: 100%; + display: grid; + grid-template-columns: 42px minmax(0, 1fr) auto; + align-items: center; + gap: 10px; + padding: 12px 14px; + border: 0; + border-top: 1px solid rgba(217, 224, 231, 0.7); + background: transparent; + color: inherit; + text-align: left; +} + +.conversation-item.active, +.conversation-item:hover { + background: #eaf3f1; +} + +.favorite-item { + width: 100%; + display: grid; + grid-template-columns: 32px minmax(0, 1fr) auto; + align-items: center; + gap: 10px; + padding: 12px 14px; + border: 0; + border-top: 1px solid rgba(217, 224, 231, 0.7); + background: transparent; + color: inherit; + text-align: left; +} + +.favorite-item.active, +.favorite-item:hover { + background: #eaf3f1; +} + +.favorite-item-icon { + width: 32px; + height: 32px; + display: grid; + place-items: center; + border-radius: 6px; + background: #fff7df; + color: #c79520; + font-size: 16px; +} + +.favorite-item-main { + min-width: 0; + display: grid; + gap: 4px; +} + +.favorite-item-main strong, +.favorite-item-main small { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.favorite-item-main strong { + font-size: 14px; +} + +.favorite-item-main small, +.favorite-item time { + color: var(--muted); + font-size: 12px; +} + +.favorite-panel { + min-width: 0; + min-height: 0; + display: grid; + grid-template-rows: auto minmax(0, 1fr); + background: #eef3f6; +} + +.favorite-detail { + min-height: 0; + display: grid; + align-content: start; + gap: 16px; + padding: 28px; + overflow: auto; +} + +.favorite-detail-message { + width: min(680px, 100%); + display: grid; + grid-template-columns: auto minmax(0, 1fr); + gap: 10px 12px; + padding: 18px; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--panel); + box-shadow: 0 8px 24px rgba(24, 33, 47, 0.08); +} + +.favorite-detail-message.recalled { + border-color: #f1c7c1; + background: #fff7f6; +} + +.favorite-detail-star { + color: #c79520; + font-size: 18px; + line-height: 1.4; +} + +.favorite-detail-body { + min-width: 0; + display: grid; + gap: 10px; +} + +.favorite-detail-message p { + margin: 0; + white-space: pre-wrap; + overflow-wrap: anywhere; + line-height: 1.55; +} + +.favorite-detail-message time { + grid-column: 2; + color: var(--muted); + font-size: 12px; +} + +.favorite-detail-image { + width: min(420px, 100%); + display: block; + overflow: hidden; + border: 1px solid #b9ded8; + border-radius: 8px; + background: #fff; +} + +.favorite-detail-image img { + display: block; + width: 100%; + max-height: 360px; + object-fit: contain; + background: #f8fbfc; +} + +.favorite-detail-file { + width: min(360px, 100%); +} + +.favorite-detail-actions { + width: min(680px, 100%); + display: flex; + align-items: center; + justify-content: flex-end; + gap: 8px; +} + +.favorite-detail-actions .inline-save-status { + margin-right: auto; +} + +.empty-list, +.empty-chat { + display: grid; + place-items: center; + align-content: center; + gap: 8px; + color: var(--muted); + text-align: center; +} + +.empty-list { + min-height: 180px; + padding: 24px; +} + +.empty-chat { + min-height: 0; + padding: 24px; +} + +.empty-list strong, +.empty-chat strong { + color: var(--text); +} + +.conversation-avatar { + width: 42px; + height: 42px; + border-radius: 8px; + background: #dce8f2; + color: #24435c; + font-weight: 700; +} + +.conversation-copy { + min-width: 0; + display: grid; + gap: 4px; +} + +.conversation-copy strong, +.conversation-copy small { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.conversation-copy small { + color: var(--muted); +} + +.unread-badge { + justify-self: end; + min-width: 20px; + height: 20px; + padding: 0 6px; + border-radius: 999px; + background: #ef4444; + color: #fff; + font-size: 12px; + font-weight: 800; + line-height: 20px; + text-align: center; + box-shadow: 0 0 0 2px #f7fafc; +} + +.chat-panel { + position: relative; + min-width: 0; + min-height: 0; + height: 100%; + display: grid; + grid-template-rows: 72px minmax(0, 1fr) auto auto; + overflow: hidden; + background: #eef2f5; +} + +.contact-panel { + min-width: 0; + min-height: 0; + height: 100%; + display: grid; + grid-template-rows: 72px minmax(0, 1fr); + overflow: auto; + background: #eef2f5; +} + +.contact-profile { + align-self: start; + width: min(520px, calc(100% - 48px)); + margin: 48px auto; + display: grid; + justify-items: center; + gap: 18px; + padding: 32px; + background: var(--panel); + border: 1px solid var(--line); + border-radius: 8px; +} + +.profile-avatar { + width: 76px; + height: 76px; + display: grid; + place-items: center; + border-radius: 8px; + background: #dce8f2; + color: #24435c; + font-size: 28px; + font-weight: 800; +} + +.profile-main { + text-align: center; +} + +.remark-row { + min-width: 0; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; +} + +.profile-main h3 { + margin: 0; + font-size: 22px; + overflow-wrap: anywhere; +} + +.remark-edit-button { + width: 32px; + height: 32px; + flex: 0 0 auto; + font-size: 16px; +} + +.profile-main p { + margin: 6px 0 0; + color: var(--muted); +} + +.profile-fields { + width: 100%; + display: grid; + gap: 12px; + margin: 0; +} + +.profile-fields div { + display: grid; + grid-template-columns: 80px minmax(0, 1fr); + gap: 12px; + padding: 10px 0; + border-top: 1px solid var(--line); +} + +.profile-fields dt { + color: var(--muted); +} + +.profile-fields dd { + margin: 0; + overflow-wrap: anywhere; +} + +.profile-action { + width: 160px; +} + +.contact-panel .contact-modal-backdrop { + position: fixed; +} + +.header-actions { + display: flex; + gap: 8px; +} + +.chat-header-actions { + position: relative; +} + +.chat-title-button { + display: grid; + grid-template-columns: 38px minmax(0, 1fr); + align-items: center; + gap: 10px; + max-width: min(460px, 52vw); + padding: 0; + border: 0; + background: transparent; + color: var(--text); + text-align: left; +} + +.chat-title-avatar { + width: 38px; + height: 38px; + display: grid; + place-items: center; + border-radius: 8px; + background: #dce8f2; + color: #24435c; + font-size: 16px; + font-weight: 800; +} + +.chat-title-copy { + min-width: 0; +} + +.chat-title-button h2 { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.chat-title-button:hover h2 { + color: var(--primary-strong); +} + +.chat-more-menu { + top: 38px; + width: 150px; +} + +.action-menu-anchor { + position: relative; +} + +.quick-action-menu { + position: absolute; + top: 40px; + right: 0; + z-index: 7; + width: 168px; + display: grid; + padding: 6px; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--panel); + box-shadow: 0 14px 34px rgba(24, 33, 47, 0.16); +} + +.quick-action-menu button { + height: 34px; + padding: 0 10px; + border: 0; + border-radius: 6px; + background: transparent; + color: var(--text); + text-align: left; +} + +.quick-action-menu button:hover { + background: #edf2f5; +} + +.quick-action-menu.chat-more-menu { + top: 38px; + width: 150px; +} + +.conversation-action-panel { + width: min(100%, 340px); + max-height: calc(100dvh - 116px); + overflow: auto; +} + +.contact-picker { + display: grid; + gap: 10px; +} + +.contact-picker > input { + width: 100%; + height: 38px; + padding: 0 10px; + border: 1px solid var(--line); + border-radius: 6px; + outline: none; +} + +.contact-picker-list { + max-height: 220px; + overflow: auto; + display: grid; + gap: 6px; + padding-right: 2px; +} + +.contact-picker-row { + width: 100%; + min-height: 48px; + display: grid; + grid-template-columns: 22px 36px minmax(0, 1fr); + align-items: center; + gap: 8px; + padding: 6px; + border: 1px solid var(--line); + border-radius: 6px; + background: #fff; + color: var(--text); + text-align: left; +} + +.contact-picker-row:hover, +.contact-picker-row.selected { + border-color: #9fd4cc; + background: #edf8f5; +} + +.contact-picker-row .conversation-avatar { + width: 36px; + height: 36px; + border-radius: 6px; + font-size: 13px; +} + +.contact-check { + width: 20px; + height: 20px; + display: grid; + place-items: center; + border: 1px solid var(--line); + border-radius: 4px; + color: var(--primary-strong); + font-size: 13px; + font-weight: 800; +} + +.contact-picker-copy { + min-width: 0; + display: grid; + gap: 2px; +} + +.contact-picker-copy strong, +.contact-picker-copy small { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.contact-picker-copy small { + color: var(--muted); + font-size: 12px; +} + +.contact-picker-empty { + min-height: 64px; + display: grid; + place-items: center; + color: var(--muted); + border: 1px dashed var(--line); + border-radius: 6px; +} + +.group-dialog { + width: min(420px, calc(100% - 32px)); + max-height: calc(100dvh - 116px); + display: grid; + gap: 16px; + padding: 18px; + overflow: auto; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--panel); + box-shadow: 0 18px 40px rgba(24, 33, 47, 0.18); +} + +.group-dialog header, +.group-dialog footer { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.group-dialog h3 { + margin: 0; + font-size: 18px; +} + +.single-profile-dialog { + width: min(420px, calc(100% - 32px)); +} + +.single-profile-summary { + display: grid; + justify-items: center; + gap: 10px; +} + +.single-profile-summary strong { + max-width: 100%; + overflow-wrap: anywhere; + font-size: 18px; +} + +.single-profile-footer { + align-items: center; + justify-content: flex-end; +} + +.single-profile-footer .inline-save-status { + margin-right: auto; +} + +.message-detail-dialog { + width: min(560px, calc(100% - 32px)); +} + +.message-detail-meta div { + grid-template-columns: 88px minmax(0, 1fr); +} + +.message-detail-muted { + color: var(--muted); +} + +.message-detail-summary { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.message-detail-summary span { + min-height: 30px; + display: inline-flex; + align-items: center; + gap: 4px; + padding: 0 10px; + border: 1px solid var(--line); + border-radius: 6px; + background: #f8fbfb; + color: var(--muted); + font-size: 13px; +} + +.message-detail-summary strong { + color: var(--text); +} + +.message-detail-groups { + display: grid; + gap: 14px; +} + +.message-detail-section { + display: grid; + gap: 8px; +} + +.message-detail-section header { + min-height: 28px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + border-bottom: 1px solid var(--line); +} + +.message-detail-section header strong { + font-size: 14px; +} + +.message-detail-section header span { + color: var(--muted); + font-size: 12px; +} + +.message-detail-member-list { + display: grid; + overflow: hidden; + border: 1px solid var(--line); + border-radius: 6px; + background: #fff; +} + +.message-detail-member-row { + display: grid; + grid-template-columns: 34px minmax(0, 1fr) auto auto; + align-items: center; + gap: 8px; + min-height: 48px; + padding: 7px 8px; + border-top: 1px solid var(--line); +} + +.message-detail-member-row:first-child { + border-top: 0; +} + +.message-detail-member-row .conversation-avatar { + width: 34px; + height: 34px; + border-radius: 6px; + font-size: 13px; +} + +.message-detail-member-main { + min-width: 0; + display: grid; + gap: 2px; +} + +.message-detail-member-main strong, +.message-detail-member-main small { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.message-detail-member-main strong { + font-size: 13px; +} + +.message-detail-member-main small, +.message-detail-member-time { + color: var(--muted); + font-size: 12px; +} + +.message-detail-member-time { + white-space: nowrap; +} + +.message-detail-role { + min-height: 22px; + display: inline-flex; + align-items: center; + padding: 0 7px; + border: 1px solid #c7d9e5; + border-radius: 6px; + color: #416272; + font-size: 12px; + white-space: nowrap; +} + +.group-dialog label { + display: grid; + gap: 8px; + color: var(--muted); + font-size: 13px; +} + +.group-dialog input { + width: 100%; + height: 42px; + padding: 0 12px; + border: 1px solid var(--line); + border-radius: 6px; + outline: none; +} + +.group-dialog textarea, +.group-dialog select { + width: 100%; + border: 1px solid var(--line); + border-radius: 6px; + outline: none; + background: #fff; + color: var(--text); +} + +.group-dialog textarea { + min-height: 86px; + resize: vertical; + padding: 10px 12px; + line-height: 1.5; +} + +.group-dialog select { + height: 42px; + padding: 0 10px; +} + +.group-dialog input:focus { + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(23, 125, 114, 0.12); +} + +.group-dialog textarea:focus, +.group-dialog select:focus { + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(23, 125, 114, 0.12); +} + +.group-profile { + display: grid; + gap: 10px; + margin: 0; +} + +.group-profile div { + display: grid; + grid-template-columns: 78px minmax(0, 1fr); + gap: 12px; + padding: 10px 0; + border-top: 1px solid var(--line); +} + +.group-profile dt { + color: var(--muted); +} + +.group-profile dd { + margin: 0; + overflow-wrap: anywhere; +} + +.group-loading { + min-height: 96px; + display: grid; + place-items: center; + color: var(--muted); +} + +.group-member-list { + display: grid; + gap: 8px; +} + +.group-member-row { + display: grid; + grid-template-columns: 42px minmax(0, 1fr); + align-items: center; + gap: 10px; + min-height: 54px; + padding: 8px; + border: 1px solid var(--line); + border-radius: 6px; + background: #fff; +} + +.group-member-row.selectable { + cursor: pointer; +} + +.group-member-row.with-check { + grid-template-columns: 24px 42px minmax(0, 1fr); +} + +.group-member-row.selectable:hover, +.group-member-row.selectable.selected { + border-color: #9fd4cc; + background: #edf8f5; +} + +.group-member-row input[type="checkbox"] { + justify-self: center; + width: 16px; + height: 16px; +} + +.group-member-row span:last-child { + min-width: 0; + display: grid; + gap: 3px; +} + +.group-member-row strong, +.group-member-row small { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.group-member-row small { + color: var(--muted); + font-size: 12px; +} + +.group-management-dialog { + width: min(520px, calc(100% - 32px)); +} + +.group-management-tabs { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.group-management-tabs button { + height: 34px; + padding: 0 10px; + border: 1px solid var(--line); + border-radius: 6px; + background: #fff; + color: var(--text); +} + +.group-management-tabs button.active, +.group-management-tabs button:hover { + border-color: var(--primary); + background: #e8f4f2; + color: var(--primary-strong); + font-weight: 700; +} + +.group-manage-section { + display: grid; + gap: 14px; +} + +.group-manage-section footer { + justify-content: flex-end; +} + +.mute-save-footer { + align-items: center; + gap: 10px; +} + +.inline-save-status { + color: var(--primary-strong); + font-size: 13px; +} + +.inline-save-status.error { + color: var(--accent); +} + +.mute-time-grid { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); + gap: 10px; +} + +.message-list { + min-height: 0; + overflow: auto; + overscroll-behavior: contain; + padding: 24px; +} + +.message-row { + display: flex; + align-items: flex-end; + gap: 8px; + margin-bottom: 14px; +} + +.message-row.out { + justify-content: flex-end; +} + +.message-sender-avatar { + width: 34px; + height: 34px; + flex: 0 0 auto; + display: grid; + place-items: center; + overflow: hidden; + padding: 0; + border: 0; + border-radius: 8px; + background: #dce8f2; + color: #24435c; + font-size: 14px; + font-weight: 800; +} + +.message-sender-avatar img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.message-stack { + max-width: min(68%, 720px); + display: grid; + justify-items: start; + gap: 4px; +} + +.message-row.out .message-stack { + justify-items: end; +} + +.message-sender-name { + min-height: 18px; + max-width: 100%; + padding: 0; + border: 0; + background: transparent; + color: var(--muted); + font-size: 12px; + font-weight: 700; + text-align: left; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.message-sender-name:hover { + color: var(--primary-strong); +} + +.message-sender-role { + margin-left: 4px; + color: var(--primary-strong); + font-size: 11px; +} + +.message-row.selected .message-bubble { + box-shadow: 0 0 0 2px rgba(24, 138, 121, 0.26); +} + +.message-row.attention .message-bubble { + animation: message-attention 1.4s ease-in-out 2; + box-shadow: 0 0 0 3px rgba(24, 138, 121, 0.3); +} + +@keyframes message-attention { + 0%, + 100% { + box-shadow: 0 0 0 2px rgba(24, 138, 121, 0.16); + } + + 45% { + box-shadow: 0 0 0 5px rgba(24, 138, 121, 0.36); + } +} + +.message-select { + width: 24px; + height: 24px; + display: grid; + place-items: center; +} + +.message-select input { + width: 16px; + height: 16px; +} + +.message-bubble { + position: relative; + max-width: min(68%, 720px); + padding: 10px 12px 8px; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--bubble-in); + box-shadow: 0 2px 8px rgba(30, 40, 50, 0.05); +} + +.message-stack .message-bubble { + max-width: 100%; +} + +.message-row.out .message-bubble { + background: var(--bubble-out); + border-color: #bfe4dc; +} + +.message-bubble:hover { + border-color: #9fd4cc; +} + +.message-bubble.role-bubble-owner, +.message-bubble.role-bubble-admin { + overflow: hidden; +} + +.message-bubble.role-bubble-owner::after, +.message-bubble.role-bubble-admin::after { + content: ""; + position: absolute; + top: 0; + right: 0; + width: 0; + height: 0; + border-top: 16px solid #d79d22; + border-left: 16px solid transparent; +} + +.message-bubble.role-bubble-admin::after { + border-top-color: #178a79; +} + +.message-bubble p { + margin: 0; + line-height: 1.5; + white-space: pre-wrap; + overflow-wrap: anywhere; +} + +.message-image { + padding: 0; + border: 0; + display: block; + max-width: min(280px, 62vw); + overflow: hidden; + border-radius: 6px; + background: #edf2f5; + cursor: zoom-in; +} + +.message-image + p, +.message-file + p { + margin-top: 8px; +} + +.message-image img { + display: block; + max-width: 100%; + max-height: 320px; + object-fit: contain; +} + +.image-viewer-backdrop { + position: fixed; + inset: 0; + z-index: 80; + display: grid; + place-items: center; + padding: 24px; + background: rgba(12, 18, 26, 0.58); +} + +.image-viewer-dialog { + width: min(880px, calc(100vw - 48px)); + height: min(640px, calc(100vh - 48px)); + min-width: 360px; + min-height: 280px; + max-width: calc(100vw - 48px); + max-height: calc(100vh - 48px); + display: grid; + grid-template-rows: 52px minmax(0, 1fr); + overflow: hidden; + resize: both; + border: 1px solid rgba(255, 255, 255, 0.18); + border-radius: 8px; + background: #101820; + box-shadow: 0 24px 70px rgba(0, 0, 0, 0.36); +} + +.image-viewer-toolbar { + min-width: 0; + display: flex; + align-items: center; + justify-content: flex-end; + gap: 8px; + padding: 8px 10px; + border-bottom: 1px solid rgba(255, 255, 255, 0.12); + background: rgba(24, 33, 47, 0.94); +} + +.image-viewer-title { + min-width: 0; + flex: 1; + overflow: hidden; + color: #f8fafc; + font-size: 13px; + font-weight: 700; + text-overflow: ellipsis; + white-space: nowrap; +} + +.image-viewer-toolbar button { + width: 36px; + height: 36px; + display: grid; + place-items: center; + border: 0; + border-radius: 6px; + background: transparent; + color: #f8fafc; + font-size: 17px; + font-weight: 700; + line-height: 1; +} + +.image-viewer-toolbar button:hover { + background: rgba(255, 255, 255, 0.14); +} + +.image-viewer-stage { + min-width: 0; + min-height: 0; + display: grid; + place-items: center; + overflow: hidden; + background: #0c121a; + cursor: default; + touch-action: none; +} + +.image-viewer-stage.zoomed { + cursor: grab; +} + +.image-viewer-stage.dragging { + cursor: grabbing; +} + +.image-viewer-img { + max-width: calc(100% - 32px); + max-height: calc(100% - 32px); + object-fit: contain; + transform-origin: center center; + transition: transform 0.08s ease-out; + user-select: none; + pointer-events: none; +} + +.image-viewer-stage.dragging .image-viewer-img { + transition: none; +} + +.sender-profile-backdrop { + position: fixed; + inset: 0; + z-index: 84; + display: grid; + place-items: center; + padding: 24px; + background: rgba(24, 33, 47, 0.32); +} + +.sender-profile-dialog { + width: min(380px, calc(100vw - 32px)); + display: grid; + gap: 16px; + padding: 18px; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--panel); + box-shadow: 0 18px 40px rgba(24, 33, 47, 0.18); +} + +.sender-profile-dialog header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.sender-profile-main { + display: grid; + justify-items: center; + gap: 8px; + text-align: center; +} + +.sender-profile-main strong { + max-width: 100%; + overflow-wrap: anywhere; + font-size: 18px; +} + +.sender-profile-main span { + color: var(--primary-strong); + font-size: 12px; + font-weight: 700; +} + +.sender-profile-fields { + display: grid; + gap: 8px; + margin: 0; +} + +.sender-profile-fields div { + display: grid; + grid-template-columns: 72px minmax(0, 1fr); + gap: 10px; + padding-top: 8px; + border-top: 1px solid var(--line); +} + +.sender-profile-fields dt { + color: var(--muted); +} + +.sender-profile-fields dd { + margin: 0; + overflow-wrap: anywhere; +} + +.sender-profile-alias { + display: grid; + gap: 8px; + color: var(--muted); + font-size: 13px; +} + +.sender-profile-alias input { + width: 100%; + height: 40px; + padding: 0 12px; + border: 1px solid var(--line); + border-radius: 6px; + outline: none; + background: #fff; + color: var(--text); +} + +.sender-profile-alias input:focus { + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(23, 125, 114, 0.12); +} + +.sender-profile-actions { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 8px; +} + +.sender-profile-actions .inline-save-status { + margin-right: auto; +} + +@media (max-width: 480px) { + .image-viewer-backdrop { + padding: 12px; + } + + .image-viewer-dialog { + width: calc(100vw - 24px); + height: min(560px, calc(100vh - 24px)); + min-width: 280px; + min-height: 240px; + max-width: calc(100vw - 24px); + max-height: calc(100vh - 24px); + } + + .image-viewer-toolbar { + gap: 4px; + padding: 6px; + } + + .image-viewer-toolbar button { + width: 32px; + height: 32px; + font-size: 15px; + } + + .message-detail-member-row { + grid-template-columns: 34px minmax(0, 1fr); + } + + .message-detail-member-time, + .message-detail-role { + grid-column: 2; + justify-self: start; + } + + .avatar-cropper-panel { + width: calc(100vw - 24px); + max-height: calc(100dvh - 24px); + } + + .avatar-cropper-layout { + grid-template-columns: minmax(0, 1fr); + } + + .avatar-cropper-stage { + height: 300px; + min-height: 300px; + } + + .avatar-cropper-controls { + grid-template-columns: minmax(0, 1fr) auto; + align-items: center; + } + + .avatar-cropper-tools { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + .avatar-cropper-preview { + width: 82px; + height: 82px; + } + + .avatar-cropper-controls footer { + grid-column: 1 / -1; + } + + .settings-panel { + width: calc(100vw - 24px); + max-height: calc(100dvh - 24px); + } + + .settings-layout { + grid-template-columns: minmax(0, 1fr); + } + + .settings-menu { + grid-auto-flow: column; + grid-auto-columns: minmax(120px, 1fr); + overflow-x: auto; + padding-bottom: 2px; + } + + .settings-password-form footer .inline-save-status { + flex: 1 1 100%; + } +} + +.message-file { + width: min(280px, 62vw); + display: grid; + grid-template-columns: 38px minmax(0, 1fr); + align-items: center; + gap: 10px; + padding: 10px; + border: 1px solid #d6e2e7; + border-radius: 6px; + background: #f8fbfc; + color: var(--text); + text-decoration: none; +} + +.message-file:hover { + border-color: #9fd4cc; + background: #edf8f5; +} + +.message-file-icon { + width: 38px; + height: 38px; + display: grid; + place-items: center; + border-radius: 6px; + background: #e3eff2; + color: var(--primary-strong); + font-size: 18px; + font-weight: 800; +} + +.message-file-main { + min-width: 0; + display: grid; + gap: 3px; +} + +.message-file-main strong, +.message-file-main small { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.message-file-main strong { + font-size: 13px; +} + +.message-file-main small { + color: var(--muted); + font-size: 11px; +} + +.message-bubble time { + display: flex; + align-items: center; + justify-content: flex-end; + flex-wrap: wrap; + gap: 4px; + margin-top: 4px; + color: var(--muted); + font-size: 11px; + text-align: right; +} + +.recall-mark { + margin-right: auto; + color: #d93025; + font-size: 11px; + font-weight: 700; + line-height: 1; +} + +.edit-mark { + color: #7a8792; + font-size: 11px; + font-weight: 600; + line-height: 1; +} + +.delivery-mark { + display: inline-flex; + align-items: center; + justify-content: flex-end; + min-width: 14px; + color: #8f9aa3; + font-size: 14px; + font-weight: 800; + letter-spacing: 0; + line-height: 1; +} + +.delivery-mark-delivered, +.delivery-mark-read { + min-width: 19px; +} + +.delivery-mark-read { + color: #1aa66a; +} + +.favorite-mark { + color: #c79520; + font-size: 12px; + line-height: 1; +} + +.delivery-check { + display: inline-block; +} + +.delivery-check-second { + margin-left: -5px; +} + +.delivery-error { + color: #c0392b; +} + +.message-context-menu { + position: fixed; + z-index: 30; + width: 136px; + padding: 6px; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--panel); + box-shadow: 0 16px 40px rgba(24, 33, 47, 0.18); +} + +.message-context-menu button { + width: 100%; + min-height: 32px; + padding: 0 10px; + border: 0; + border-radius: 6px; + background: transparent; + color: var(--text); + text-align: left; +} + +.message-context-menu button:hover { + background: #eaf3f1; +} + +.selection-toolbar { + min-height: 42px; + display: flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + border-top: 1px solid var(--line); + background: #f7fafc; +} + +.selection-toolbar span { + flex: 1; + color: var(--muted); + font-size: 13px; +} + +.selection-toolbar button { + min-height: 30px; + padding: 0 10px; + border-radius: 6px; +} + +.forward-dialog, +.edit-dialog { + width: min(100%, 340px); + max-height: min(520px, calc(100vh - 130px)); + display: grid; + gap: 4px; + padding: 14px; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--panel); + box-shadow: 0 18px 40px rgba(24, 33, 47, 0.18); + overflow: auto; +} + +.forward-dialog header, +.edit-dialog header, +.forward-target { + display: flex; + align-items: center; + gap: 10px; +} + +.forward-dialog header, +.edit-dialog header { + justify-content: space-between; + margin-bottom: 6px; +} + +.forward-dialog h3, +.edit-dialog h3 { + margin: 0; + font-size: 16px; +} + +.edit-dialog { + width: min(100%, 420px); + gap: 12px; +} + +.edit-dialog textarea { + width: 100%; + min-height: 132px; + resize: vertical; + border-radius: 8px; + border: 1px solid var(--line); + padding: 10px; + background: #f9fbfc; + color: var(--text); + line-height: 1.5; +} + +.edit-dialog textarea:focus { + border-color: var(--accent); + outline: 0; + box-shadow: 0 0 0 3px rgba(24, 138, 121, 0.14); +} + +.edit-dialog footer { + display: flex; + justify-content: flex-end; + gap: 8px; +} + +.edit-dialog footer button { + min-height: 34px; + padding: 0 14px; + border-radius: 8px; +} + +.forward-target { + min-height: 52px; + padding: 6px; + border: 0; + border-radius: 8px; + background: transparent; + color: var(--text); + text-align: left; +} + +.forward-target:hover { + background: #eaf3f1; +} + +.chat-toast { + position: absolute; + left: 50%; + bottom: 132px; + z-index: 20; + transform: translateX(-50%); + padding: 8px 12px; + border-radius: 8px; + background: rgba(35, 45, 58, 0.92); + color: #fff; + font-size: 13px; + pointer-events: none; +} + +.chat-toast.error { + background: rgba(192, 57, 43, 0.94); +} + +.message-composer { + position: relative; + min-height: 0; + border-top: 1px solid var(--line); + background: var(--panel); +} + +.message-composer::after { + content: ""; + position: absolute; + inset: 6px; + z-index: 4; + border: 2px dashed transparent; + border-radius: 8px; + pointer-events: none; + transition: border-color 0.12s ease, background-color 0.12s ease; +} + +.message-composer.drop-active::after { + border-color: var(--primary); + background: rgba(23, 125, 114, 0.08); +} + +.quote-preview { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 16px 0; + color: var(--muted); + font-size: 13px; +} + +.quote-preview span { + min-width: 0; + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + border-left: 3px solid var(--accent); + padding-left: 8px; +} + +.quote-preview strong { + margin-right: 6px; + color: var(--text); +} + +.quote-preview button { + width: 26px; + height: 26px; + border: 0; + border-radius: 6px; + background: transparent; + color: var(--muted); + font-size: 18px; +} + +.quote-preview button:hover { + background: #edf2f5; +} + +.composer-tools { + position: relative; + display: flex; + gap: 4px; + padding: 8px 16px 0; +} + +.composer-tools input[type="file"] { + display: none; +} + +.composer-tools button { + width: 32px; + height: 32px; + border: 0; + border-radius: 6px; + background: transparent; + color: var(--muted); + font-size: 18px; +} + +.composer-tools button:hover { + background: #edf2f5; +} + +.emoji-picker { + position: absolute; + left: 16px; + bottom: 38px; + z-index: 12; + width: 256px; + display: grid; + grid-template-columns: repeat(8, 1fr); + gap: 2px; + padding: 8px; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--panel); + box-shadow: 0 14px 34px rgba(24, 33, 47, 0.16); +} + +.emoji-picker button { + width: 28px; + height: 28px; + border-radius: 6px; + font-size: 17px; +} + +.message-composer textarea { + display: block; + min-height: 86px; + max-height: 180px; + padding: 12px 16px; + border: 0; + border-radius: 0; + resize: none; + box-shadow: none; +} + +.composer-footer { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 0 16px 14px; + color: var(--muted); + font-size: 12px; +} + +.send-button { + min-width: 72px; + height: 34px; +} + +@media (max-width: 760px) { + .app-shell { + grid-template-columns: 56px minmax(0, 1fr); + } + + .conversation-list { + display: none; + } + + .message-bubble { + max-width: 86%; + } +} diff --git a/vue.config.js b/vue.config.js new file mode 100644 index 0000000..b73dc92 --- /dev/null +++ b/vue.config.js @@ -0,0 +1,51 @@ +const path = require('path'); +const webpack = require('webpack'); + +module.exports = { + outputDir: 'dist', + publicPath: './', + transpileDependencies: true, + configureWebpack: { + resolve: { + alias: { + '@': path.resolve(__dirname, 'src'), + 'legacy-wfc': path.resolve(__dirname, '../src/wfc'), + 'legacy-config': path.resolve(__dirname, '../src/config.js') + }, + fallback: { + path: false, + fs: false, + assert: false, + util: false, + os: false, + crypto: false, + buffer: require.resolve('buffer/') + } + }, + plugins: [ + new webpack.ProvidePlugin({ + process: 'process/browser', + Buffer: ['buffer', 'Buffer'] + }) + ] + }, + devServer: { + allowedHosts: 'all', + client: { + overlay: false + }, + proxy: { + '/local-api': { + target: 'http://127.0.0.1:8024', + changeOrigin: true, + pathRewrite: { + '^/local-api': '/api' + } + } + } + }, + chainWebpack(config) { + config.optimization.concatenateModules(false); + config.optimization.minimize(false); + } +};