feat: 使用 FloatButton 上传 PageSpy 日志

2.0/email-builder
Jimmy Liow 12 months ago
parent 7c3572bd3f
commit e5a48f9ac2

@ -2,7 +2,15 @@ import ErrorBoundary from '@/components/ErrorBoundary'
import useAuthStore from '@/stores/AuthStore'
import useConversationStore from '@/stores/ConversationStore'
import { useThemeContext } from '@/stores/ThemeContext'
import { App as AntApp, ConfigProvider, Empty, theme } from 'antd'
import {
App as AntApp,
ConfigProvider,
Empty,
message,
FloatButton,
theme,
} from 'antd'
import { BugOutlined } from '@ant-design/icons'
import zhLocale from 'antd/locale/zh_CN'
import 'dayjs/locale/zh-cn'
import { useEffect } from 'react'
@ -15,44 +23,56 @@ import '@/assets/App.css'
import 'react-chat-elements/dist/main.css'
function AuthApp() {
const navigate = useNavigate()
const [messageApi, contextHolder] = message.useMessage()
const { colorPrimary, borderRadius } = useThemeContext()
const loginUser = useAuthStore(state => state.loginUser)
const loginUser = useAuthStore((state) => state.loginUser)
const href = useHref()
const [connectWebsocket, fetchInitialData, disconnectWebsocket ] = useConversationStore((state) => [
state.connectWebsocket,
state.fetchInitialData,
state.disconnectWebsocket,
]);
const [connectWebsocket, fetchInitialData, disconnectWebsocket] =
useConversationStore((state) => [
state.connectWebsocket,
state.fetchInitialData,
state.disconnectWebsocket,
])
useEffect(() => {
if (!("Notification" in window)) {
if (!('Notification' in window)) {
// alert("This browser does not support desktop notification");
} else {
Notification.requestPermission();
Notification.requestPermission()
}
if (loginUser.userId > 0) {
appendRequestHeader('X-User-Id', loginUser.userId);
loadPageSpy(loginUser.username);
connectWebsocket(loginUser.userId);
fetchInitialData(loginUser.userId);
appendRequestHeader('X-User-Id', loginUser.userId)
loadPageSpy(loginUser.username)
connectWebsocket(loginUser.userId)
fetchInitialData(loginUser.userId)
}
return () => {
disconnectWebsocket();
};
disconnectWebsocket()
}
}, [])
const uploadLog = () => {
if (window.$pageSpy) {
window.$pageSpy.triggerPlugins('onOfflineLog', 'upload')
messageApi.info('Success')
} else {
messageApi.error('Failure')
}
}
// /p...
const needToLogin = (loginUser.userId === -1) && (href.indexOf('/p/') === -1)
const needToLogin = loginUser.userId === -1 && href.indexOf('/p/') === -1
useEffect(() => {
if (needToLogin) {
navigate('/p/dingding/login?origin_url=' + href)
}
}, [href])
}, [href])
return (
<ConfigProvider
@ -66,20 +86,40 @@ function AuthApp() {
algorithm: theme.defaultAlgorithm,
}}
locale={zhLocale}
renderEmpty={() => <Empty description={false} />}>
renderEmpty={() => <Empty description={false} />}
>
<AntApp>
<ErrorBoundary>
<FloatButton.Group
shape="square"
style={{
insetInlineEnd: 94,
}}
>
<FloatButton icon={<BugOutlined />} onClick={() => uploadLog()} />
<FloatButton.BackTop />
</FloatButton.Group>
{contextHolder}
{needToLogin ? <>login...</> : <Outlet />}
<dialog id='about-dialog' className='border-0'>
<img className='logo' src={AppLogo} alt='logo' />
<section className='about'>
<dialog id="about-dialog" className="border-0">
<img className="logo" src={AppLogo} alt="logo" />
<section className="about">
<h1>销售平台</h1>
<h2>Sales CRM</h2>
<p>Haina travel global sales CRM system</p>
</section>
<form className='actions flex gap-1' method='dialog'>
<button value='cancel' className='px-4 py-2 rounded-full border-0'>Close</button>
<button value='install' id='install-button' className='px-4 py-2 rounded-full border-0 border-transparent bg-indigo-500 text-white'>
<form className="actions flex gap-1" method="dialog">
<button
value="cancel"
className="px-4 py-2 rounded-full border-0"
>
Close
</button>
<button
value="install"
id="install-button"
className="px-4 py-2 rounded-full border-0 border-transparent bg-indigo-500 text-white"
>
Install app
</button>
</form>
@ -87,7 +127,7 @@ function AuthApp() {
</ErrorBoundary>
</AntApp>
</ConfigProvider>
);
)
}
export default AuthApp

@ -2,7 +2,18 @@ import useAuthStore from '@/stores/AuthStore'
import useConversationStore from '@/stores/ConversationStore'
import { useThemeContext } from '@/stores/ThemeContext'
import { DownOutlined } from '@ant-design/icons'
import { Avatar, Col, Dropdown, Layout, Menu, Row, Space, Typography, theme, Badge } from 'antd'
import {
Avatar,
Col,
Dropdown,
Layout,
Menu,
Row,
Space,
Typography,
theme,
Badge,
} from 'antd'
import 'dayjs/locale/zh-cn'
import { useEffect, useState } from 'react'
import { Link, NavLink, Outlet, useHref } from 'react-router-dom'
@ -10,9 +21,8 @@ import { Link, NavLink, Outlet, useHref } from 'react-router-dom'
import '@/assets/App.css'
import AppLogo from '@/assets/highlights_travel_300_300.png'
import 'react-chat-elements/dist/main.css'
import ReloadPrompt from './ReloadPrompt';
import ClearCache from './ClearCache';
import PageSpy from './PageSpy';
import ReloadPrompt from './ReloadPrompt'
import ClearCache from './ClearCache'
import { BUILD_VERSION, BUILD_DATE } from '@/config'
@ -20,9 +30,8 @@ const { Header, Footer, Content } = Layout
const { Title } = Typography
function DesktopApp() {
const { colorPrimary } = useThemeContext()
const loginUser = useAuthStore(state => state.loginUser)
const loginUser = useAuthStore((state) => state.loginUser)
const href = useHref()
@ -31,7 +40,7 @@ function DesktopApp() {
let defaultPath = '/order/follow'
if (href !== '/') {
const splitPath = href.split('/');
const splitPath = href.split('/')
if (splitPath.length > 2) {
defaultPath = '/' + splitPath[1] + '/' + splitPath[2]
}
@ -44,46 +53,60 @@ function DesktopApp() {
/**
* 标签页标题闪烁
*/
const [isTitleVisible, setIsTitleVisible] = useState(true);
const [isTitleVisible, setIsTitleVisible] = useState(true)
useEffect(() => {
let interval;
let interval
if (totalNotify > 0) {
if ('setAppBadge' in navigator) {
navigator.setAppBadge(totalNotify).catch((error) => {});
navigator.setAppBadge(totalNotify).catch((error) => {})
}
interval = setInterval(() => {
document.title = isTitleVisible ? `🔔🔥💬【${totalNotify}条新消息】` : '______________';
setIsTitleVisible(!isTitleVisible);
}, 500);
document.title = isTitleVisible
? `🔔🔥💬【${totalNotify}条新消息】`
: '______________'
setIsTitleVisible(!isTitleVisible)
}, 500)
} else {
document.title = '销售平台';
document.title = '销售平台'
if ('clearAppBadge' in navigator) {
navigator.clearAppBadge().catch((error) => {});
navigator.clearAppBadge().catch((error) => {})
}
}
return () => clearInterval(interval);
}, [totalNotify, isTitleVisible]);
return () => clearInterval(interval)
}, [totalNotify, isTitleVisible])
return (
<Layout>
<Header className='header' style={{ position: 'sticky', top: 0, zIndex: 2, width: '100%', background: 'white' }}>
<Row gutter={{ md: 24 }} align='middle'>
<Col flex='220px'>
<NavLink to='/'>
<img src={AppLogo} className='logo' alt='App logo' />
<Header
className="header"
style={{
position: 'sticky',
top: 0,
zIndex: 2,
width: '100%',
background: 'white',
}}
>
<Row gutter={{ md: 24 }} align="middle">
<Col flex="220px">
<NavLink to="/">
<img src={AppLogo} className="logo" alt="App logo" />
</NavLink>
<Title level={3}>销售平台</Title>
</Col>
<Col span={10}>
<Menu
mode='horizontal'
mode="horizontal"
selectedKeys={[defaultPath]}
items={[
{ key: '/order/follow', label: <Link to='/order/follow'>订单跟踪</Link> },
{
key: '/order/follow',
label: <Link to="/order/follow">订单跟踪</Link>,
},
{
key: '/order/chat',
label: (
<Link to='/order/chat'>
<Link to="/order/chat">
在线聊天
<Badge
count={totalNotify > 0 ? totalNotify : undefined}
@ -94,18 +117,32 @@ function DesktopApp() {
</Link>
),
},
{ key: '/callcenter/call', label: <Link to='/callcenter/call'>语音通话</Link> },
{ key: '/chat/history', label: <Link to='/chat/history'>聊天记录</Link> },
{
key: '/callcenter/call',
label: <Link to="/callcenter/call">语音通话</Link>,
},
{
key: '/chat/history',
label: <Link to="/chat/history">聊天记录</Link>,
},
]}
/>
</Col>
<Col flex='auto' style={{ color: 'white', marginBottom: '0', display: 'flex', justifyContent: 'end' }}>
<Col
flex="auto"
style={{
color: 'white',
marginBottom: '0',
display: 'flex',
justifyContent: 'end',
}}
>
<ReloadPrompt />
<Dropdown
menu={{
items: [
{
label: <Link to='/account/profile'>个人资料</Link>,
label: <Link to="/account/profile">个人资料</Link>,
key: '1',
},
{ type: 'divider' },
@ -114,15 +151,21 @@ function DesktopApp() {
{ label: <ClearCache />, key: 'clearcache' },
{ type: 'divider' },
{
label: <Link to='/p/dingding/logout'>退出</Link>,
label: <Link to="/p/dingding/logout">退出</Link>,
key: '3',
},
],
}}
trigger={['click']}>
<a onClick={(e) => e.preventDefault()} style={{ color: colorPrimary }}>
trigger={['click']}
>
<a
onClick={(e) => e.preventDefault()}
style={{ color: colorPrimary }}
>
<Space>
<Avatar src={loginUser.avatarUrl}>{loginUser?.username?.substring(1)}</Avatar>
<Avatar src={loginUser.avatarUrl}>
{loginUser?.username?.substring(1)}
</Avatar>
{loginUser.username}
<DownOutlined />
</Space>
@ -138,13 +181,16 @@ function DesktopApp() {
margin: 0,
minHeight: 280,
background: colorBgContainer,
}}>
}}
>
<Outlet />
</Content>
</Layout>
<Footer>桂林海纳国际旅行社有限公司 Version: {BUILD_VERSION}({BUILD_DATE}){' '}<PageSpy /></Footer>
<Footer>
桂林海纳国际旅行社有限公司 Version: {BUILD_VERSION}({BUILD_DATE})
</Footer>
</Layout>
);
)
}
export default DesktopApp

@ -1,59 +1,74 @@
import { useEffect } from 'react';
import '@/assets/App.css';
import AppLogo from '@/assets/highlights_travel_300_300.png';
import { useThemeContext } from '@/stores/ThemeContext';
import useAuthStore from '@/stores/AuthStore';
import { Layout, Typography, theme, Space, Avatar, Dropdown, Flex } from 'antd';
import { DownOutlined } from '@ant-design/icons';
import { NavLink, Outlet, Link } from 'react-router-dom';
import ReloadPrompt from './ReloadPrompt';
import ClearCache from './ClearCache';
import PageSpy from './PageSpy';
import { useEffect } from 'react'
import '@/assets/App.css'
import AppLogo from '@/assets/highlights_travel_300_300.png'
import { useThemeContext } from '@/stores/ThemeContext'
import useAuthStore from '@/stores/AuthStore'
import { Layout, Typography, theme, Space, Avatar, Dropdown, Flex } from 'antd'
import { DownOutlined } from '@ant-design/icons'
import { NavLink, Outlet, Link } from 'react-router-dom'
import ReloadPrompt from './ReloadPrompt'
import ClearCache from './ClearCache'
import { BUILD_VERSION } from '@/config';
import { BUILD_VERSION } from '@/config'
const { Header, Footer, Content } = Layout;
const { Title } = Typography;
const { Header, Footer, Content } = Layout
const { Title } = Typography
function MobileApp() {
const { colorPrimary, borderRadius } = useThemeContext();
const loginUser = useAuthStore((state) => state.loginUser);
const { colorPrimary, borderRadius } = useThemeContext()
const loginUser = useAuthStore((state) => state.loginUser)
const {
token: { colorBgContainer },
} = theme.useToken();
} = theme.useToken()
useEffect(() => {
const handleLoad = () => {
const isPWAInstalled = window.matchMedia('(display-mode: window-controls-overlay)').matches || window.matchMedia('(display-mode: standalone)').matches;
const isStandalone = navigator.standalone || window.navigator.standalone;
const isPWAInstalled =
window.matchMedia('(display-mode: window-controls-overlay)').matches ||
window.matchMedia('(display-mode: standalone)').matches
const isStandalone = navigator.standalone || window.navigator.standalone
if (isPWAInstalled || isStandalone) {
document.getElementById('install-button').disabled = true;
document.getElementById('install-button').disabled = true
} else {
document.getElementById('about-dialog').showModal();
document.getElementById('about-dialog').showModal()
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
e.preventDefault()
document.getElementById('about-dialog').addEventListener('close', () => {
if (document.getElementById('about-dialog').returnValue === 'install') {
e.prompt();
}
});
});
document
.getElementById('about-dialog')
.addEventListener('close', () => {
if (
document.getElementById('about-dialog').returnValue ===
'install'
) {
e.prompt()
}
})
})
}
};
}
window.addEventListener('load', handleLoad);
return () => window.removeEventListener('load', handleLoad);
}, []);
window.addEventListener('load', handleLoad)
return () => window.removeEventListener('load', handleLoad)
}, [])
return (
<Layout>
<Header className='header px-2' style={{ position: 'sticky', top: 0, zIndex: 1, width: '100%', background: 'white' }}>
<Header
className="header px-2"
style={{
position: 'sticky',
top: 0,
zIndex: 1,
width: '100%',
background: 'white',
}}
>
<Flex justify={'space-between'}>
<NavLink to='/'>
<img src={AppLogo} className='logo' alt='App logo' />
<NavLink to="/">
<img src={AppLogo} className="logo" alt="App logo" />
{!('Notification' in window) && <span>🔕</span>}
</NavLink>
<ReloadPrompt />
@ -64,17 +79,21 @@ function MobileApp() {
{ type: 'divider' },
{ label: <ClearCache />, key: 'clearcache' },
{ type: 'divider' },
{ label: <Link to='/p/dingding/logout'>退出</Link>, key: '3' },
{ label: <Link to="/p/dingding/logout">退出</Link>, key: '3' },
{ type: 'divider' },
{ label: <>v{BUILD_VERSION}</>, key: 'BUILD_VERSION' },
{ type: 'divider' },
{ label: <PageSpy />, key: 'pagespy' },
],
}}
trigger={['click']}>
<a onClick={(e) => e.preventDefault()} style={{ color: colorPrimary }}>
trigger={['click']}
>
<a
onClick={(e) => e.preventDefault()}
style={{ color: colorPrimary }}
>
<Space>
<Avatar src={loginUser.avatarUrl}>{loginUser?.username?.substring(1)}</Avatar>
<Avatar src={loginUser.avatarUrl}>
{loginUser?.username?.substring(1)}
</Avatar>
{loginUser.username}
<DownOutlined />
</Space>
@ -93,13 +112,14 @@ function MobileApp() {
margin: 0,
minHeight: 200,
background: colorBgContainer,
}}>
}}
>
<Outlet />
</Content>
</Layout>
{/* <Footer>桂林海纳国际旅行社有限公司</Footer> */}
</Layout>
);
)
}
export default MobileApp;
export default MobileApp

@ -1,17 +0,0 @@
const PageSpyLog = () => {
return (
<>
{window.$pageSpy && (
<a
className='text-primary'
onClick={() => {
window.$pageSpy.triggerPlugins('onOfflineLog', 'download');
window.$pageSpy.triggerPlugins('onOfflineLog', 'upload');
}}>
上传Debug日志 ({window.$pageSpy.address.substring(0, 4)})
</a>
)}
</>
);
};
export default PageSpyLog;
Loading…
Cancel
Save