feat: 小程序 webview 内点全屏监控自动 CSS 横屏,版本号 1.1.4
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
小程序 webview 无法调用系统旋转 API,竖屏全屏体验很差。检测到微信/抖音/ 支付宝小程序 UA 且当前为竖屏时,全屏覆盖层用 transform: rotate(90deg) 配合 100vh × 100vw 的尺寸模拟真横屏,用户用横屏姿势看设备即可获得横屏 监控面板。浏览器会自动把触摸坐标映射回旋转前坐标系,交互不受影响。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "ln-bi",
|
"name": "ln-bi",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.1.3",
|
"version": "1.1.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "concurrently -n server,client -c blue,green \"npm run dev:server\" \"npm run dev:client\"",
|
"dev": "concurrently -n server,client -c blue,green \"npm run dev:server\" \"npm run dev:client\"",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import type { MonitoringVehicle, MonitoringStats, MonitoringFilters } from './types';
|
import type { MonitoringVehicle, MonitoringStats, MonitoringFilters } from './types';
|
||||||
import { fetchMonitoring } from './api';
|
import { fetchMonitoring } from './api';
|
||||||
|
import Blur from '../../components/Blur';
|
||||||
|
|
||||||
const SearchableSelect = ({
|
const SearchableSelect = ({
|
||||||
options,
|
options,
|
||||||
@@ -278,6 +279,20 @@ export default function MonitoringView() {
|
|||||||
return () => { document.body.style.overflow = ''; };
|
return () => { document.body.style.overflow = ''; };
|
||||||
}, [isFullscreen]);
|
}, [isFullscreen]);
|
||||||
|
|
||||||
|
// 检测是否在小程序 webview 中(微信/抖音/支付宝等),且当前是竖屏
|
||||||
|
// 小程序 webview 无法调用系统旋转 API,只能用 CSS rotate 强制横屏
|
||||||
|
const forceLandscape = useMemo(() => {
|
||||||
|
if (typeof window === 'undefined') return false;
|
||||||
|
const ua = navigator.userAgent || '';
|
||||||
|
const isMiniProgram =
|
||||||
|
/miniProgram/i.test(ua) ||
|
||||||
|
/toutiaomicroapp/i.test(ua) ||
|
||||||
|
/AlipayClient/i.test(ua) ||
|
||||||
|
(window as any).__wxjs_environment === 'miniprogram';
|
||||||
|
const isPortrait = window.innerHeight > window.innerWidth;
|
||||||
|
return isMiniProgram && isPortrait;
|
||||||
|
}, [isFullscreen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* 顶部哨兵:离开视口时显示回到顶部按钮 */}
|
{/* 顶部哨兵:离开视口时显示回到顶部按钮 */}
|
||||||
@@ -290,7 +305,20 @@ export default function MonitoringView() {
|
|||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
exit={{ opacity: 0 }}
|
exit={{ opacity: 0 }}
|
||||||
className="fixed inset-0 z-[100] bg-slate-950 flex flex-col overflow-hidden"
|
className="fixed z-[100] bg-slate-950 flex flex-col overflow-hidden"
|
||||||
|
style={
|
||||||
|
forceLandscape
|
||||||
|
? {
|
||||||
|
// 小程序 webview 无法真横屏,强制 CSS 旋转 90 度模拟横屏
|
||||||
|
top: 0,
|
||||||
|
left: '100vw',
|
||||||
|
width: '100vh',
|
||||||
|
height: '100vw',
|
||||||
|
transform: 'rotate(90deg)',
|
||||||
|
transformOrigin: 'top left',
|
||||||
|
}
|
||||||
|
: { top: 0, left: 0, right: 0, bottom: 0 }
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{/* Top bar: compact inline KPI */}
|
{/* Top bar: compact inline KPI */}
|
||||||
<div className="flex-shrink-0 px-3 py-2 border-b border-slate-800/60 flex items-center justify-between">
|
<div className="flex-shrink-0 px-3 py-2 border-b border-slate-800/60 flex items-center justify-between">
|
||||||
@@ -458,8 +486,8 @@ export default function MonitoringView() {
|
|||||||
<td className="px-3 py-2 text-center">
|
<td className="px-3 py-2 text-center">
|
||||||
<div className={`w-2 h-2 rounded-full mx-auto ${v.isOnline ? 'bg-green-500 shadow-[0_0_6px_rgba(34,197,94,0.4)]' : v.isDataSynced ? 'bg-slate-600' : 'bg-amber-400 animate-pulse'}`}></div>
|
<div className={`w-2 h-2 rounded-full mx-auto ${v.isOnline ? 'bg-green-500 shadow-[0_0_6px_rgba(34,197,94,0.4)]' : v.isDataSynced ? 'bg-slate-600' : 'bg-amber-400 animate-pulse'}`}></div>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-2 text-xs font-bold text-white">{v.plate}</td>
|
<td className="px-3 py-2 text-xs font-bold text-white"><Blur>{v.plate}</Blur></td>
|
||||||
<td className="px-3 py-2 text-[11px] text-slate-400">{v.customer || '-'}</td>
|
<td className="px-3 py-2 text-[11px] text-slate-400"><Blur>{v.customer || '-'}</Blur></td>
|
||||||
<td className="px-3 py-2 text-[11px] text-slate-400">{v.rentStatus || '-'}</td>
|
<td className="px-3 py-2 text-[11px] text-slate-400">{v.rentStatus || '-'}</td>
|
||||||
<td className="px-3 py-2 text-[11px] text-slate-400">{v.department || '-'}</td>
|
<td className="px-3 py-2 text-[11px] text-slate-400">{v.department || '-'}</td>
|
||||||
<td className="px-3 py-2 text-right">
|
<td className="px-3 py-2 text-right">
|
||||||
@@ -829,14 +857,14 @@ export default function MonitoringView() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="overflow-hidden flex-1">
|
<div className="overflow-hidden flex-1">
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<span className="text-xs font-black text-slate-900 font-mono">{v.plate}</span>
|
<span className="text-xs font-black text-slate-900 font-mono"><Blur>{v.plate}</Blur></span>
|
||||||
<span className={`text-[8px] px-1 rounded ${v.isOnline ? 'bg-green-50 text-green-600' : 'bg-slate-100 text-slate-400'} font-bold`}>
|
<span className={`text-[8px] px-1 rounded ${v.isOnline ? 'bg-green-50 text-green-600' : 'bg-slate-100 text-slate-400'} font-bold`}>
|
||||||
{v.isOnline ? '在线' : '离线'}
|
{v.isOnline ? '在线' : '离线'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<span className="text-[8px] text-slate-300 font-bold">{v.rentStatus || ''}{v.department ? ` · ${v.department.replace('业务', '')}` : ''}</span>
|
<span className="text-[8px] text-slate-300 font-bold">{v.rentStatus || ''}{v.department ? ` · ${v.department.replace('业务', '')}` : ''}</span>
|
||||||
<span className="text-[9px] font-bold text-slate-600 truncate">{v.customer || '-'}</span>
|
<span className="text-[9px] font-bold text-slate-600 truncate"><Blur>{v.customer || '-'}</Blur></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user