认证域:
- 找回密码验证码请求存在竞态:请求发出后如果用户改了手机号,旧请求
返回时仍会用旧手机号的challenge�covers新手机号的输入框,现在按
发起请求时的手机号快照判断,作废过期响应
- 切换企业码时"退出登录"确认框可被绕过:判断依据只有内存里的
myUserId,直接刷新在/tenant页会让这个值变回null,导致确认框不弹
就静默销毁一个其实还有效的会话;现在同时检查sessionStorage里的
token
- 企业码不存在错误文案缺后半句",请核实后重试",和其它两处文案对齐
- 营业执照上传补上前端体积/类型校验(仿照问题反馈页截图校验的写法),
不再无条件发一次注定被拒的上传请求
- 找回密码页移动端"重置密码"标题重复渲染两次,桌面版标题补上
min-[900px]:block hidden
- 企业码输入框补maxLength=64
图标CSS:Material Icons的.im-icon规则漏抄了Google原始样式表的两行
(-webkit-font-feature-settings/-webkit-font-smoothing),补齐让
"1:1 copy"真正成立
顶号踢人 vs 已有401登出的竞态:两条路径会同时清空同一个token,如果
401先触发,用户会被直接甩回登录页看不到"账号已在其他设备登录"的
提示——forceReauth()现在先等一小段时间看kickedInfo是否被顶号流程
设置,设置了就让顶号流程自己的按钮触发跳转,避免抢跑
通话信令健壮性:sendCallRinging/Accept/Hangup底层用的{note}包没有id
字段,连接断开时会同步抛异常而不是走Promise reject——之前hangUp()
在这类异常后直接中断,导致后续的leaveCall()清理和UI收尾全部被跳过。
统一加了try/catch兜底
消息核心:
- 名片消息/通话结果消息(非Drafty的扁平自定义对象)之前可以被引用/
复制/转发,会把这些对象错误地喂给Drafty的解析函数,产生格式错误
的内容——现在这两类消息只保留收藏/撤回/删除/多选
- MessagesLayout.tsx自己独立计算的peerName没有对slf(自聊天)做
isSelf特殊处理,会显示裸topic id"slf"而不是昵称,和已经修过的
ConversationView头部不一致,现在补齐同样的处理
- 发送失败图标/文案错用了danger-text(撤回删除等破坏性操作的语义
token),改成语义正确的unread token;录音条文字被连带染红,只保留
指示点是红色
- 群成员数徽标形状(应为4px圆角矩形不是胶囊)和颜色(应为text-tertiary-4
不是text-tertiary-3)都和设计稿不符,已修正
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
168 lines
5.7 KiB
CSS
168 lines
5.7 KiB
CSS
@import "tailwindcss";
|
|
|
|
/*
|
|
* Material Icons' OWN Google Fonts stylesheet (index.html's `<link>`) ships its `.material-icons`
|
|
* class completely unlayered -- plain CSS, no `@layer`. Tailwind v4 (the `@import` above) puts every
|
|
* one of its own utilities inside `@layer utilities`. Per the CSS Cascade Layers spec, ANY unlayered
|
|
* declaration beats EVERY layered one for the same property on the same element, regardless of
|
|
* selector specificity or which stylesheet loads later in the document -- confirmed live: an
|
|
* `<Icon className="text-[14px]">` computed to `font-size: 24px` regardless of the requested size,
|
|
* every time, because Google's unlayered rule was always winning over Tailwind's layered
|
|
* `.text-\[14px\]` utility. Icon.tsx uses its own `im-icon` class (not `.material-icons`) specifically
|
|
* so Google's competing rule never matches these elements at all -- this is the same visual default
|
|
* (24px, line-height 1, etc., copied 1:1 from Google's own stylesheet), just declared inside `@layer
|
|
* base` so Tailwind's `@layer utilities` (which Tailwind itself registers after `base`) can actually
|
|
* override it, the way every call site sizing an icon via `text-[Npx]` already assumed it could.
|
|
*/
|
|
@layer base {
|
|
.im-icon {
|
|
font-family: 'Material Icons';
|
|
font-weight: normal;
|
|
font-style: normal;
|
|
font-size: 24px;
|
|
line-height: 1;
|
|
letter-spacing: normal;
|
|
text-transform: none;
|
|
display: inline-block;
|
|
white-space: nowrap;
|
|
word-wrap: normal;
|
|
direction: ltr;
|
|
-webkit-font-feature-settings: 'liga';
|
|
-webkit-font-smoothing: antialiased;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Design tokens ported 1:1 from docs/design_handoff_meetingim_web/README.md's "Design Tokens"
|
|
* section. Values are exact hex/px from the design handoff, not semantic guesses -- keep them in
|
|
* sync with that doc, not with whatever the previous im_webapp's css/base.css happened to use.
|
|
*
|
|
* Breakpoints are intentionally remapped onto Tailwind's own sm/md/lg so every component can just
|
|
* use the standard `sm:`/`md:`/`lg:` prefixes (min-width, mobile-first) instead of inventing custom
|
|
* variants: <640 base styles = single column mobile, sm: (>=640) = two column, md: (>=960) = three
|
|
* column, lg: (>=1280) = four column docked detail panel.
|
|
*/
|
|
@theme {
|
|
--breakpoint-sm: 640px;
|
|
--breakpoint-md: 960px;
|
|
--breakpoint-lg: 1280px;
|
|
|
|
--font-sans: Roboto, 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
|
|
|
/* Primary / accent */
|
|
--color-accent: #1677ff;
|
|
--color-accent-soft-bg: #e3eefe;
|
|
--color-accent-selected-bg: #e8f1fe;
|
|
--color-focus-border: #4091ff;
|
|
|
|
--color-btn: #368bfa;
|
|
--color-btn-hover: #1677e8;
|
|
--color-btn-disabled: #b8d5fa;
|
|
|
|
/* Sent bubble */
|
|
--color-bubble-mine: #b9eb8f;
|
|
|
|
/* Surfaces */
|
|
--color-surface-chat: #eef1f4;
|
|
--color-surface-content: #f6f7f9;
|
|
--color-surface-page: #f7f9fc;
|
|
--color-surface-rail: #f2f4f7;
|
|
--color-surface-input: #f2f4f7;
|
|
--color-surface-search: #f1f2f4;
|
|
|
|
/* Borders */
|
|
--color-border-panel: #eceef1;
|
|
--color-border-row: #f1f2f4;
|
|
--color-border-list-item: #f4f5f7;
|
|
|
|
/* Text */
|
|
--color-text-title: #14171c;
|
|
--color-text-heading: #22262d;
|
|
--color-text-body: #252931;
|
|
--color-text-secondary: #656b74;
|
|
--color-text-tertiary-1: #4a4f57;
|
|
--color-text-tertiary-2: #969ca5;
|
|
--color-text-tertiary-3: #9ca2aa;
|
|
--color-text-tertiary-4: #8d949d;
|
|
--color-text-placeholder-1: #b2b7bf;
|
|
--color-text-placeholder-2: #a5abb4;
|
|
|
|
--color-icon-default: #555c65;
|
|
--color-icon-arrow: #c2c7ce;
|
|
|
|
/* Status */
|
|
--color-unread: #f34b46;
|
|
--color-danger-text: #d94b4b;
|
|
--color-danger-border: #cf3f3f;
|
|
--color-danger-border-soft: #f3c4c4;
|
|
--color-online: #1fb36a;
|
|
--color-offline: #aeb4bd;
|
|
|
|
/* Orange (system messages / group owner / warning) */
|
|
--color-warn: #ff951c;
|
|
--color-warn-bg: #fff5e5;
|
|
--color-announce: #f5a623;
|
|
--color-announce-bg: #fff8e6;
|
|
--color-announce-text: #6d4c41;
|
|
|
|
/* Green (file assistant / success) */
|
|
--color-success: #16ae54;
|
|
--color-success-bg: #eefaf3;
|
|
|
|
/* Alert strips */
|
|
--color-error-strip-bg: #ffeef0;
|
|
--color-error-strip-text: #b51c1c;
|
|
--color-info-strip-bg: #e8f1fe;
|
|
--color-info-strip-text: #1a4f9c;
|
|
|
|
/* Call surfaces (dark) */
|
|
--color-call-bg-1: #1b2231;
|
|
--color-call-bg-2: #1f2a3d;
|
|
--color-call-bg-3: #3b4a63;
|
|
|
|
/* Avatar palette (deterministic per-contact hashing picks from this list) */
|
|
--color-avatar-1: #3c8df6;
|
|
--color-avatar-2: #7c5cf6;
|
|
--color-avatar-3: #2bb673;
|
|
--color-avatar-4: #f28c3b;
|
|
--color-avatar-5: #e2537a;
|
|
--color-avatar-6: #0097a8;
|
|
--color-avatar-7: #8d6e63;
|
|
--color-avatar-8: #5c6bc0;
|
|
--color-avatar-9: #26a69a;
|
|
--color-avatar-10: #ef6c00;
|
|
--color-avatar-11: #9ca2aa;
|
|
|
|
/* Shadows */
|
|
--shadow-card: 0 8px 32px rgba(35, 74, 125, 0.10);
|
|
--shadow-modal: 0 20px 60px rgba(0, 0, 0, 0.18);
|
|
--shadow-menu: 0 12px 36px rgba(0, 0, 0, 0.16);
|
|
--shadow-bubble: 0 1px 2px rgba(0, 0, 0, 0.06);
|
|
/* screens/01-account.dc.html's 50px auth CTAs (登录/注册/提交...). */
|
|
--shadow-btn: 0 6px 16px rgba(54, 139, 250, 0.2);
|
|
/* screens/02-messages.dc.html's 36px 发送 button (ConversationView/VoiceRecorder) -- a smaller
|
|
button gets a smaller shadow, not the same token as the 50px auth buttons above. */
|
|
--shadow-btn-sm: 0 4px 12px rgba(54, 139, 250, 0.2);
|
|
--shadow-focus-ring: 0 0 0 3px rgba(22, 119, 255, 0.1);
|
|
}
|
|
|
|
/* MessageRow's jump-to-message flash (ConversationView, 查找聊天记录 result click) -- a brief
|
|
background pulse marking the message the user asked to find, fading back to transparent so it
|
|
doesn't look like a permanent selection state. */
|
|
@keyframes jump-highlight {
|
|
0%,
|
|
30% {
|
|
background-color: var(--color-accent-soft-bg);
|
|
}
|
|
100% {
|
|
background-color: transparent;
|
|
}
|
|
}
|
|
|
|
body {
|
|
margin: 0;
|
|
background: var(--color-surface-page);
|
|
color: var(--color-text-body);
|
|
font-family: var(--font-sans);
|
|
}
|