chore: hide debug overlays, prepare for production

This commit is contained in:
2026-06-25 20:29:54 +08:00
parent 9a14a75b42
commit bba368b98c
2 changed files with 34 additions and 10 deletions

View File

@@ -159,18 +159,20 @@ server: {
- CRMEB 客户端**任何异常都优雅降级**(返回空昵称/头像,不阻断登录),并 `logger.warning` 记录,便于排查。 - CRMEB 客户端**任何异常都优雅降级**(返回空昵称/头像,不阻断登录),并 `logger.warning` 记录,便于排查。
- `DEV_MODE=1` 才启用 `POST /api/auth/dev-login`,生产必须为 `0`。 - `DEV_MODE=1` 才启用 `POST /api/auth/dev-login`,生产必须为 `0`。
- **CRMEB 匹配口径**:openid 按 appid 隔离。若 CRMEB 与小程序非同一 appid,应改用 **unionid** 匹配(小程序传 unionid,CRMEB 内部接口按 unionid 查;openid 仍作本系统主键)。 - **CRMEB 匹配口径**:openid 按 appid 隔离。若 CRMEB 与小程序非同一 appid,应改用 **unionid** 匹配(小程序传 unionid,CRMEB 内部接口按 unionid 查;openid 仍作本系统主键)。
- **H5 端 openid 一致性检查**(分享场景必做):当 H5 被其他用户分享的链接打开时,web-view 的 `localStorage` 可能残留原用户的 token。H5 的路由守卫(如 `ProtectedRoute`)必须比对**当前 URL 中的 openid** 与 **本地缓存 token 中的 openid**,不一致时清除缓存重新走登录流程: - **H5 端 openid 一致性检查**(分享场景必做):当 H5 被其他用户分享的链接打开时,web-view 的 `localStorage` 可能残留原用户的 token。检查位置应在 **LoginPage 的 `useEffect` 最开头**(比路由守卫更早触发,覆盖 `/` 公开路由),比对 URL 中的 openid 与缓存 token 中的 openid,不一致或 token 解码失败时都清除缓存:
```tsx ```tsx
const urlOpenid = new URLSearchParams(window.location.search).get('openid'); const urlOpenid = new URLSearchParams(window.location.search).get('openid');
if (urlOpenid && token) { const cachedToken = getToken();
const payload = decodeToken(token); if (urlOpenid && cachedToken) {
if (payload.openid !== urlOpenid) { try {
clearToken(); // 清除原用户缓存 const payload = decodeToken(cachedToken);
return <Navigate to="/" replace />; // 重新登录 if (payload.openid !== urlOpenid) clearToken();
} catch (e) {
clearToken();
} }
} }
``` ```
> 否则会出现:B 打开 A 分享的链接,URL 中已是 B 的 openid,但 H5 读取到 A 的缓存 token,仍然展示 A 的数据。 > 否则会出现:B 打开 A 分享的链接,web-view 的 localStorage 仍有 A 的 token,导致 H5 展示 A 的数据。
--- ---
@@ -291,6 +293,17 @@ server {
location /app_03/ { location /app_03/ {
alias /opt/app_03/frontend/dist/; alias /opt/app_03/frontend/dist/;
try_files $uri $uri/ /app_03/index.html; try_files $uri $uri/ /app_03/index.html;
# index.html 不缓存(每次从服务器拉最新,确保 JS hash 更新)
location = /app_03/index.html {
add_header Cache-Control "no-cache, must-revalidate";
}
# 带 hash 的静态资源可长期缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
} }
# API 代理(proxy_pass 带尾 /api/,前缀被替换) # API 代理(proxy_pass 带尾 /api/,前缀被替换)
@@ -768,6 +781,15 @@ async function handleCopyLink(url: string) {
### 12.7 微信 web-view 分享限制 ### 12.7 微信 web-view 分享限制
| 分享方式 | 支持 | 备注 |
|---------|:----:|------|
| 转发给朋友("..."菜单) | ✅ | 需实现 `onShareAppMessage` + `uni.showShareMenu()` |
| 分享到朋友圈("..."菜单) | ❌ | **微信平台限制**,所有 web-view 页面均不支持 |
| H5 内部"分享给好友"按钮 | ✅ | 通过 `bridge.share()` → postMessage → `uni.share()` |
| H5 内部复制链接 | ✅ | `document.execCommand('copy')` 主方案 + bridge 兜底 |
| 生成分享图→存相册→手动发圈 | ✅ | 替代方案,需 `html-to-image` 等截图库 |
| 分享方式 | 支持 | 备注 | | 分享方式 | 支持 | 备注 |
|---------|:----:|------| |---------|:----:|------|
| 转发给朋友("..."菜单) | ✅ | 需实现 `onShareAppMessage` + `uni.showShareMenu()` | | 转发给朋友("..."菜单) | ✅ | 需实现 `onShareAppMessage` + `uni.showShareMenu()` |
@@ -788,4 +810,6 @@ async function handleCopyLink(url: string) {
- [ ] WebView Bridge: H5 复制链接优先用 `document.execCommand('copy')`,bridge 兜底 - [ ] WebView Bridge: H5 复制链接优先用 `document.execCommand('copy')`,bridge 兜底
- [ ] WebView Bridge: 确认微信 JS-SDK 已加载(`jweixin-1.2.0.js`),否则 `wx.miniProgram` 不可用 - [ ] WebView Bridge: 确认微信 JS-SDK 已加载(`jweixin-1.2.0.js`),否则 `wx.miniProgram` 不可用
- [ ] WebView Bridge: 生成分享图片场景使用 `html-to-image` 等截图库 - [ ] WebView Bridge: 生成分享图片场景使用 `html-to-image` 等截图库
- [ ] H5 身份一致性: 路由守卫中比对 URL openid 与缓存 token openid,不一致时清除缓存重新登录 - [ ] H5 身份一致性: LoginPage 的 useEffect 中比对 URL openid 与缓存 token openid,不一致或解码失败时清除缓存重新登录
- [ ] Nginx: /app_03/index.html 设 `Cache-Control: no-cache`,静态资源设 far-future cache

View File

@@ -9,8 +9,8 @@ export function MobileLayout() {
return ( return (
<div className="max-w-md mx-auto min-h-dvh relative bg-morandi-50 shadow-[0_0_40px_rgba(0,0,0,0.06)]"> <div className="max-w-md mx-auto min-h-dvh relative bg-morandi-50 shadow-[0_0_40px_rgba(0,0,0,0.06)]">
{/* 全局调试浮层:所有页面下可见 */} {/* 调试浮层(已隐藏,保留代码以备后续调试) */}
{debugInfo.length > 0 && ( {false && debugInfo.length > 0 && (
<div <div
className="absolute top-0 left-0 right-0 z-50 bg-black/80 text-green-400 text-xs p-3 overflow-auto max-h-[50%] cursor-pointer font-mono" className="absolute top-0 left-0 right-0 z-50 bg-black/80 text-green-400 text-xs p-3 overflow-auto max-h-[50%] cursor-pointer font-mono"
style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-all', lineHeight: 1.5 }} style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-all', lineHeight: 1.5 }}